2009-10-30 12 views
28

誰でも、PostgreSQLの数値配列をHibernate経由でjavaの数値配列にマッピングできましたか?PostgreSQLアレイとHibernateのマッピング

SQL:

CREATE TABLE sal_emp (name text, pay_by_quarter integer[]); 
INSERT INTO sal_emp VALUES ('one', '{1,2,3}'); 
INSERT INTO sal_emp VALUES ('two', '{4,5,6}'); 
INSERT INTO sal_emp VALUES ('three', '{2,4,6}'); 

マッピング:

<hibernate-mapping> 
    <class name="SalEmp" table="sal_emp"> 
     <id name="name" /> 
     <property name="payByQuarter" column="pay_by_quarter" /> 
    </class> 
</hibernate-mapping> 

クラス:

public class SalEmp implements Serializable{ 
    private String name; 
    private Integer[] payByQuarter; 
    ...// getters & setters 
} 

テーブルを照会するとき、私は例外を取得します。

答えて

22

Hibernateは、データベース配列(例えば、java.sql.Arrayにマップされたもの)をそのまま使用することはできません。

arrayprimitive-arrayは、Java配列をバッキングテーブルにマッピングするためのもので、基本的には1対多の/要素のコレクションのバリエーションです。

最新のPostgreSQLのJDBCドライバ(8.4.whateverは)しかし、JDBC4 Connection.createArrayOf()方法だけでなく、ResultSet.getArray()PreparedStatement.setArray()方法をサポートしていますので、あなたは、配列のサポートを提供するために、独自のUserTypeを書くことができます。

Hereは、適切な開始点を提供するOracleの配列を扱うUserTypeの実装であり、代わりにjava.sql.Arrayを処理するのに合理的に単純です。

+12

誰が(それが今404です)最後のリンクが何であるか疑問に思った場合は、ウェイバックマシンのリンクがここにある - のhttp ://web.archive.org/web/20090325101739/http://www.hibernate.org/393.html – Sam

7

おそらく、これは他の人にとって役に立ちます:私のケースではパフォーマンスが悪く、c3p0で使用できませんでした。 (彼らは私を修正してください解決することができているだけ、簡単にこれらの問題を探求!)

を休止3.6:

import java.io.Serializable; 
import java.sql.Array; 
import java.sql.Connection; 
import java.sql.PreparedStatement; 
import java.sql.ResultSet; 
import java.sql.SQLException; 
import java.util.Arrays; 

import org.apache.commons.lang.ArrayUtils; 
import org.hibernate.HibernateException; 
import org.hibernate.usertype.UserType; 

public class IntArrayUserType implements UserType { 
protected static final int SQLTYPE = java.sql.Types.ARRAY; 

@Override 
public Object nullSafeGet(final ResultSet rs, final String[] names, final Object owner) throws HibernateException, SQLException { 
    Array array = rs.getArray(names[0]); 
    Integer[] javaArray = (Integer[]) array.getArray(); 
    return ArrayUtils.toPrimitive(javaArray); 
} 

@Override 
public void nullSafeSet(final PreparedStatement statement, final Object object, final int i) throws HibernateException, SQLException { 
    Connection connection = statement.getConnection(); 

    int[] castObject = (int[]) object; 
    Integer[] integers = ArrayUtils.toObject(castObject); 
    Array array = connection.createArrayOf("integer", integers); 

    statement.setArray(i, array); 
} 

@Override 
public Object assemble(final Serializable cached, final Object owner) throws HibernateException { 
    return cached; 
} 

@Override 
public Object deepCopy(final Object o) throws HibernateException { 
    return o == null ? null : ((int[]) o).clone(); 
} 

@Override 
public Serializable disassemble(final Object o) throws HibernateException { 
    return (Serializable) o; 
} 

@Override 
public boolean equals(final Object x, final Object y) throws HibernateException { 
    return x == null ? y == null : x.equals(y); 
} 

@Override 
public int hashCode(final Object o) throws HibernateException { 
    return o == null ? 0 : o.hashCode(); 
} 

@Override 
public boolean isMutable() { 
    return false; 
} 

@Override 
public Object replace(final Object original, final Object target, final Object owner) throws HibernateException { 
    return original; 
} 

@Override 
public Class<int[]> returnedClass() { 
    return int[].class; 
} 

@Override 
public int[] sqlTypes() { 
    return new int[] { SQLTYPE }; 
} 
} 
+3

'int []'が 'null'の場合、' nullSafeGet'と 'nullSafeSet'は動作しません。ヌル値をチェックする必要があります。 –

3

これは、文字列配列に対してテストされています。多分、数値配列のためにコンバータのいくつかの変更が必要です。これはSpring JPAで動作します。プロジェクト

import java.sql.ResultSet; 
import java.sql.SQLException; 
import java.util.Arrays; 
import java.util.Map; 

/** 
* This is class provides {@link java.sql.Array} interface for PostgreSQL <code>text</code> array. 
* 
* @author Valentine Gogichashvili 
* 
*/ 

public class PostgreSQLTextArray implements java.sql.Array { 

    private final String[] stringArray; 
    private final String stringValue; 

    /** 
    * Initializing constructor 
    * @param stringArray 
    */ 
    public PostgreSQLTextArray(String[] stringArray) { 
     this.stringArray = stringArray; 
     this.stringValue = stringArrayToPostgreSQLTextArray(this.stringArray); 

    } 

    @Override 
    public String toString() { 
     return stringValue; 
    } 

    private static final String NULL = "NULL"; 

    /** 
    * This static method can be used to convert an string array to string representation of PostgreSQL text array. 
    * @param a source String array 
    * @return string representation of a given text array 
    */ 
    public static String stringArrayToPostgreSQLTextArray(String[] stringArray) { 
     final int arrayLength; 
     if (stringArray == null) { 
      return NULL; 
     } else if ((arrayLength = stringArray.length) == 0) { 
      return "{}"; 
     } 
     // count the string length and if need to quote 
     int neededBufferLentgh = 2; // count the beginning '{' and the ending '}' brackets 
     boolean[] shouldQuoteArray = new boolean[stringArray.length]; 
     for (int si = 0; si < arrayLength; si++) { 
      // count the comma after the first element 
      if (si > 0) neededBufferLentgh++; 

      boolean shouldQuote; 
      final String s = stringArray[si]; 
      if (s == null) { 
       neededBufferLentgh += 4; 
       shouldQuote = false; 
      } else { 
       final int l = s.length(); 
       neededBufferLentgh += l; 
       if (l == 0 || s.equalsIgnoreCase(NULL)) { 
        shouldQuote = true; 
       } else { 
        shouldQuote = false; 
        // scan for commas and quotes 
        for (int i = 0; i < l; i++) { 
         final char ch = s.charAt(i); 
         switch(ch) { 
          case '"': 
          case '\\': 
           shouldQuote = true; 
           // we will escape these characters 
           neededBufferLentgh++; 
           break; 
          case ',': 
          case '\'': 
          case '{': 
          case '}': 
           shouldQuote = true; 
           break; 
          default: 
           if (Character.isWhitespace(ch)) { 
            shouldQuote = true; 
           } 
           break; 
         } 
        } 
       } 
       // count the quotes 
       if (shouldQuote) neededBufferLentgh += 2; 
      } 
      shouldQuoteArray[si] = shouldQuote; 
     } 

     // construct the String 
     final StringBuilder sb = new StringBuilder(neededBufferLentgh); 
     sb.append('{'); 
     for (int si = 0; si < arrayLength; si++) { 
      final String s = stringArray[si]; 
      if (si > 0) sb.append(','); 
      if (s == null) { 
       sb.append(NULL); 
      } else { 
       final boolean shouldQuote = shouldQuoteArray[si]; 
       if (shouldQuote) sb.append('"'); 
       for (int i = 0, l = s.length(); i < l; i++) { 
        final char ch = s.charAt(i); 
        if (ch == '"' || ch == '\\') sb.append('\\'); 
        sb.append(ch); 
       } 
       if (shouldQuote) sb.append('"'); 
      } 
     } 
     sb.append('}'); 
     assert sb.length() == neededBufferLentgh; 
     return sb.toString(); 
    } 


    @Override 
    public Object getArray() throws SQLException { 
     return stringArray == null ? null : Arrays.copyOf(stringArray, stringArray.length); 
    } 

    @Override 
    public Object getArray(Map<String, Class<?>> map) throws SQLException { 
     return getArray(); 
    } 

    @Override 
    public Object getArray(long index, int count) throws SQLException { 
     return stringArray == null ? null : Arrays.copyOfRange(stringArray, (int) index, (int) index + count); 
    } 

    @Override 
    public Object getArray(long index, int count, Map<String, Class<?>> map) throws SQLException { 
     return getArray(index, count); 
    } 

    @Override 
    public int getBaseType() throws SQLException { 
     return java.sql.Types.VARCHAR; 
    } 

    @Override 
    public String getBaseTypeName() throws SQLException { 
     return "text"; 
    } 

    @Override 
    public ResultSet getResultSet() throws SQLException { 
     throw new UnsupportedOperationException(); 
    } 

    @Override 
    public ResultSet getResultSet(Map<String, Class<?>> map) throws SQLException { 
     throw new UnsupportedOperationException(); 
    } 

    @Override 
    public ResultSet getResultSet(long index, int count) throws SQLException { 
     throw new UnsupportedOperationException(); 
    } 

    @Override 
    public ResultSet getResultSet(long index, int count, Map<String, Class<?>> map) throws SQLException { 
     throw new UnsupportedOperationException(); 
    } 

    @Override 
    public void free() throws SQLException { 
    } 

} 

2)あなたのコード

import org.postgresql.jdbc4.Jdbc4Array; 

import javax.persistence.AttributeConverter; 
import javax.persistence.Converter; 
import java.sql.SQLException; 
import java.util.ArrayList; 
import java.util.List; 

@Converter(autoApply = true) 
public class ListToArrayConveter implements AttributeConverter<List<String>, Object> { 
    @Override 
    public PostgreSQLTextArray convertToDatabaseColumn(List<String> attribute) { 
     if (attribute == null || attribute.isEmpty()) { 
      return null; 
     } 
     String[] rst = new String[attribute.size()]; 
     return new PostgreSQLTextArray(attribute.toArray(rst)); 
    } 

    @Override 
    public List<String> convertToEntityAttribute(Object dbData) { 

     List<String> rst = new ArrayList<>(); 
     try { 
      String[] elements = (String[]) ((Jdbc4Array) dbData).getArray(); 
      for (String element : elements) { 
       rst.add(element); 
      } 
     } catch (SQLException e) { 
      e.printStackTrace(); 
     } 


     return rst; 
    } 
} 

3)それを使用するListToArrayConverterを追加する

1)を追加PostgreSQLTextArray!ここで

@Entity 
@Table(name = "emails") 
public class Email { 

    [...] 

    @SuppressWarnings("JpaAttributeTypeInspection") 
    @Column(name = "subject", columnDefinition = "text[]") 
    @Convert(converter = ListToArrayConveter.class) 
    private List<String> subject; 

    [...] 
1

は、私はあなたにもnullSafeGet()nullSafeSet()にnullチェックが含まれている後にしているものを行うために使用されるint[]のUserTypeです:

import org.apache.commons.lang.ArrayUtils; 
import org.hibernate.HibernateException; 
import org.hibernate.engine.spi.SessionImplementor; 
import org.hibernate.usertype.UserType; 

import java.io.Serializable; 
import java.sql.Array; 
import java.sql.Connection; 
import java.sql.PreparedStatement; 
import java.sql.ResultSet; 
import java.sql.SQLException; 

public class IntegerArrayUserType implements UserType { 
    protected static final int SQLTYPE = java.sql.Types.ARRAY; 

    @Override 
    public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException { 
     Array array = rs.getArray(names[0]); 
     if (array == null) { 
      return null; 
     } 
     Integer[] javaArray = (Integer[]) array.getArray(); 
     return ArrayUtils.toPrimitive(javaArray); 
    } 

    @Override 
    public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException { 
     Connection connection = st.getConnection(); 

     if (value == null) { 
      st.setNull(index, sqlTypes()[0]); 
     } else { 
      int[] castObject = (int[]) value; 
      Integer[] integers = ArrayUtils.toObject(castObject); 
      Array array = connection.createArrayOf("integer", integers); 

      st.setArray(index, array); 
     } 
    } 

    @Override 
    public Object assemble(final Serializable cached, final Object owner) throws HibernateException { 
     return cached; 
    } 

    @Override 
    public Object deepCopy(final Object o) throws HibernateException { 
     return o == null ? null : ((int[]) o).clone(); 
    } 

    @Override 
    public Serializable disassemble(final Object o) throws HibernateException { 
     return (Serializable) o; 
    } 

    @Override 
    public boolean equals(final Object x, final Object y) throws HibernateException { 
     return x == null ? y == null : x.equals(y); 
    } 

    @Override 
    public int hashCode(final Object o) throws HibernateException { 
     return o == null ? 0 : o.hashCode(); 
    } 

    @Override 
    public boolean isMutable() { 
     return false; 
    } 

    @Override 
    public Object replace(final Object original, final Object target, final Object owner) throws HibernateException { 
     return original; 
    } 

    @Override 
    public Class<int[]> returnedClass() { 
     return int[].class; 
    } 

    @Override 
    public int[] sqlTypes() { 
     return new int[] { SQLTYPE }; 
    } 
} 
3

私は、PostgreSQL 9.4にString[]を保存することができましたし、 EclipseLink 2.6。JPAコンバータアプローチを介した2はDBにも適していますからアレイをロード

Tk421 7月1日2016年

の答えのための源であるように思わhere

を掲載しました。

<class> com.ssg.fcp.fcp_e761.therealthing.backend.jpa.convert.ListToArrayConverter </class> 

、代わりに使用してくださいもうJdbc4ArrayはPostgre JDBCドライバに存在しないことを明記してください:

org.postgresql.jdbc.PgArray 

はこちらを参照してください:で Package org.postgresql.jdbc4 is missing since 9.4-1207

7

をさらにpersistence.xmlに追加

this article、私はString[]int[]のようなさまざまなタイプに簡単に適応できる汎用Array型を思いつく方法を説明しました。 、詳細情報については

<dependency> 
    <groupId>com.vladmihalcea</groupId> 
    <artifactId>hibernate-types-52</artifactId> 
    <version>${hibernate-types.version}</version> 
</dependency> 

hibernate-types open-source projectをチェックアウト:

あなたは、単に次の依存関係を使用してMavenの中央を経由して にそれらを得ることができ、手動ですべてのこれらのタイプを作成する必要はありません。

create table event (
    id int8 not null, 
    version int4, 
    sensor_names text[], 
    sensor_values integer[], 
    primary key (id) 
) 

そして、あなたはこのようにそれをマップしたい:

@Entity(name = "Event") 
@Table(name = "event") 
@TypeDefs({ 
    @TypeDef(
     name = "string-array", 
     typeClass = StringArrayType.class 
    ), 
    @TypeDef(
     name = "int-array", 
     typeClass = IntArrayType.class 
    ) 
}) 
public static class Event 
    extends BaseEntity { 

    @Type(type = "string-array") 
    @Column(
     name = "sensor_names", 
     columnDefinition = "text[]" 
    ) 
    private String[] sensorNames; 

    @Type(type = "int-array") 
    @Column(
     name = "sensor_values", 
     columnDefinition = "integer[]" 
    ) 
    private int[] sensorValues; 

    //Getters and setters omitted for brevity 
} 

あなたはこのようなStringArrayType定義する必要があります。

をあなたのデータベースにこのテーブルを持っていると仮定すると

public class StringArrayType 
     extends AbstractSingleColumnStandardBasicType<String[]> 
     implements DynamicParameterizedType { 

    public StringArrayType() { 
     super( 
      ArraySqlTypeDescriptor.INSTANCE, 
      StringArrayTypeDescriptor.INSTANCE 
     ); 
    } 

    public String getName() { 
     return "string-array"; 
    } 

    @Override 
    protected boolean registerUnderJavaType() { 
     return true; 
    } 

    @Override 
    public void setParameterValues(Properties parameters) { 
     ((StringArrayTypeDescriptor) 
      getJavaTypeDescriptor()) 
      .setParameterValues(parameters); 
    } 
} 

を定義する必要がありますこのよう:文字列とのInt型の

public class IntArrayType 
     extends AbstractSingleColumnStandardBasicType<int[]> 
     implements DynamicParameterizedType { 

    public IntArrayType() { 
     super( 
      ArraySqlTypeDescriptor.INSTANCE, 
      IntArrayTypeDescriptor.INSTANCE 
     ); 
    } 

    public String getName() { 
     return "int-array"; 
    } 

    @Override 
    protected boolean registerUnderJavaType() { 
     return true; 
    } 

    @Override 
    public void setParameterValues(Properties parameters) { 
     ((IntArrayTypeDescriptor) 
      getJavaTypeDescriptor()) 
      .setParameterValues(parameters); 
    } 
} 

の両方がArraySqlTypeDescriptorを共有:

public class ArraySqlTypeDescriptor 
    implements SqlTypeDescriptor { 

    public static final ArraySqlTypeDescriptor INSTANCE = 
     new ArraySqlTypeDescriptor(); 

    @Override 
    public int getSqlType() { 
     return Types.ARRAY; 
    } 

    @Override 
    public boolean canBeRemapped() { 
     return true; 
    } 

    @Override 
    public <X> ValueBinder<X> getBinder(
     JavaTypeDescriptor<X> javaTypeDescriptor) { 
     return new BasicBinder<X>(javaTypeDescriptor, this) { 
      @Override 
      protected void doBind(
        PreparedStatement st, 
        X value, 
        int index, 
        WrapperOptions options 
       ) throws SQLException { 

       AbstractArrayTypeDescriptor<Object> abstractArrayTypeDescriptor = 
        (AbstractArrayTypeDescriptor<Object>) 
         javaTypeDescriptor; 

       st.setArray( 
        index, 
        st.getConnection().createArrayOf(
         abstractArrayTypeDescriptor.getSqlArrayType(), 
         abstractArrayTypeDescriptor.unwrap( 
          value, 
          Object[].class, 
          options 
         ) 
        ) 
       ); 
      } 

      @Override 
      protected void doBind(
        CallableStatement st, 
        X value, 
        String name, 
        WrapperOptions options 
       ) throws SQLException { 
       throw new UnsupportedOperationException( 
        "Binding by name is not supported!" 
       ); 
      } 
     }; 
    } 

    @Override 
    public <X> ValueExtractor<X> getExtractor(
     final JavaTypeDescriptor<X> javaTypeDescriptor) { 
     return new BasicExtractor<X>(javaTypeDescriptor, this) { 
      @Override 
      protected X doExtract(
        ResultSet rs, 
        String name, 
        WrapperOptions options 
       ) throws SQLException { 
       return javaTypeDescriptor.wrap(
        rs.getArray(name), 
        options 
       ); 
      } 

      @Override 
      protected X doExtract(
        CallableStatement statement, 
        int index, 
        WrapperOptions options 
       ) throws SQLException { 
       return javaTypeDescriptor.wrap(
        statement.getArray(index), 
        options 
       ); 
      } 

      @Override 
      protected X doExtract(
        CallableStatement statement, 
        String name, 
        WrapperOptions options 
       ) throws SQLException { 
       return javaTypeDescriptor.wrap(
        statement.getArray(name), 
        options 
       ); 
      } 
     }; 
    } 
} 

はまた、Javaの記述子を定義する必要があります。

public class StringArrayTypeDescriptor 
     extends AbstractArrayTypeDescriptor<String[]> { 

    public static final StringArrayTypeDescriptor INSTANCE = 
     new StringArrayTypeDescriptor(); 

    public StringArrayTypeDescriptor() { 
     super(String[].class); 
    } 

    @Override 
    protected String getSqlArrayType() { 
     return "text"; 
    } 
} 

public class IntArrayTypeDescriptor 
     extends AbstractArrayTypeDescriptor<int[]> { 

    public static final IntArrayTypeDescriptor INSTANCE = 
     new IntArrayTypeDescriptor(); 

    public IntArrayTypeDescriptor() { 
     super(int[].class); 
    } 

    @Override 
    protected String getSqlArrayType() { 
     return "integer"; 
    } 
} 

JavaとJDBC型処理の大部分はAbstractArrayTypeDescriptorベースクラスに含まれる:

public abstract class AbstractArrayTypeDescriptor<T> 
     extends AbstractTypeDescriptor<T> 
     implements DynamicParameterizedType { 

    private Class<T> arrayObjectClass; 

    @Override 
    public void setParameterValues(Properties parameters) { 
     arrayObjectClass = ((ParameterType) parameters 
      .get(PARAMETER_TYPE)) 
      .getReturnedClass(); 

    } 

    public AbstractArrayTypeDescriptor(Class<T> arrayObjectClass) { 
     super( 
      arrayObjectClass, 
      (MutabilityPlan<T>) new MutableMutabilityPlan<Object>() { 
       @Override 
       protected T deepCopyNotNull(Object value) { 
        return ArrayUtil.deepCopy(value); 
       } 
      } 
     ); 
     this.arrayObjectClass = arrayObjectClass; 
    } 

    @Override 
    public boolean areEqual(Object one, Object another) { 
     if (one == another) { 
      return true; 
     } 
     if (one == null || another == null) { 
      return false; 
     } 
     return ArrayUtil.isEquals(one, another); 
    } 

    @Override 
    public String toString(Object value) { 
     return Arrays.deepToString((Object[]) value); 
    } 

    @Override 
    public T fromString(String string) { 
     return ArrayUtil.fromString(
      string, 
      arrayObjectClass 
     ); 
    } 

    @SuppressWarnings({ "unchecked" }) 
    @Override 
    public <X> X unwrap(
      T value, 
      Class<X> type, 
      WrapperOptions options 
     ) { 
     return (X) ArrayUtil.wrapArray(value); 
    } 

    @Override 
    public <X> T wrap(
      X value, 
      WrapperOptions options 
     ) { 
     if(value instanceof Array) { 
      Array array = (Array) value; 
      try { 
       return ArrayUtil.unwrapArray( 
        (Object[]) array.getArray(), 
        arrayObjectClass 
       ); 
      } 
      catch (SQLException e) { 
       throw new IllegalArgumentException(e); 
      } 
     } 
     return (T) value; 
    } 

    protected abstract String getSqlArrayType(); 
} 

AbstractArrayTypeDescriptorは、Java配列ディープコピー、ラッピング及びアンラッピングロジックを処理するためArrayUtilに依存しています。

ここで、2つのエンティティを挿入すると、

Event nullEvent = new Event(); 
nullEvent.setId(0L); 
entityManager.persist(nullEvent); 

Event event = new Event(); 
event.setId(1L); 
event.setSensorNames(
    new String[] { 
     "Temperature", 
     "Pressure" 
    } 
); 
event.setSensorValues( 
    new int[] { 
     12, 
     756 
    } 
); 
entityManager.persist(event); 

Hibernateは次のSQL文を生成するために起こっている:

INSERT INTO event (
    version, 
    sensor_names, 
    sensor_values, 
    id 
) 
VALUES (
    0, 
    NULL(ARRAY), 
    NULL(ARRAY), 
    0 
) 

INSERT INTO event (
    version, 
    sensor_names, 
    sensor_values, 
    id 
) 
VALUES ( 
    0, 
    {"Temperature","Pressure"}, 
    {"12","756"}, 
    1 
) 
関連する問題