2016-08-24 26 views
5

は、私はそうのようにpostgresのテーブルを持っていると言う:Spring Data/JPAを使用してPostgres Array型の列に挿入する方法?

CREATE TABLE sal_emp (
    name   text, 
    pay_by_quarter integer[], 
    schedule  text[][] 
); 

私は偶数列pay_by_quarterまたはscheduleに挿入する春のデータを使用することができますか?可能であれば、これはどのようにリポジトリとエンティティのように見えますか?私はこれを扱うドキュメントや例を見つけることができませんでした。これはおそらくより一般的なユースケースと重複して複数のテーブルに1対多の関係として挿入するためです。私は、Postgresqlのarrayデータ型とリレーショナル表を使用するつもりです。

+1

はい、これはSpringデータで実現できます。しかし、これは1NF(https://en.wikipedia.org/wiki/First_normal_form)に違反する非常に悪い習慣であると私は示唆しています。可能であれば、モデルリビジョンを検討してください。 – crm86

+0

@ crm86私が保存している特定のデータ(Twitterからのつぶやきの大量ストリーム)に対してFNFを持たない方がよいです。私は1対多の関係を1つのテーブルに保存したい(そのように価値のないデータのために非常に多くの結合を避けるため)。私はまた、NoSQLドキュメントデータベースが私のために維持するのが難しいので避けたい。私は、 'array'データ型の別の代替手段が' JSONB'であり、すべてをpostgresqlの文書として保存すると思います。しかし、私は列(およびいくつかの配列のデータ型)を扱う方がJSON(PostgreSQL)を使うよりも優れていると思います。 – Zombies

答えて

8

独自のタイプを作成し、UserType interfaceを実装する必要があります。次にresponseに基づいて、私はすべての配列で使用する汎用UserTypeを書きましたが、それは動作しますが、プリミティブ以外のデータ型(整数、ロング、ストリングなど)を使用する必要があります。それ以外の場合は、Booleanタイプの上記アップデートを参照してください。

public class GenericArrayUserType<T extends Serializable> implements UserType { 

    protected static final int[] SQL_TYPES = { Types.ARRAY }; 
    private Class<T> typeParameterClass; 

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

    @Override 
    public Object deepCopy(Object value) throws HibernateException { 
     return value; 
    } 

    @SuppressWarnings("unchecked") 
    @Override 
    public Serializable disassemble(Object value) throws HibernateException { 
     return (T) this.deepCopy(value); 
    } 

    @Override 
    public boolean equals(Object x, Object y) throws HibernateException { 

     if (x == null) { 
      return y == null; 
     } 
     return x.equals(y); 
    } 

    @Override 
    public int hashCode(Object x) throws HibernateException { 
     return x.hashCode(); 
    } 

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

    @Override 
    public Object nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor session, Object owner) 
      throws HibernateException, SQLException { 
     if (resultSet.wasNull()) { 
      return null; 
     } 
     if (resultSet.getArray(names[0]) == null) { 
      return new Integer[0]; 
     } 

     Array array = resultSet.getArray(names[0]); 
     @SuppressWarnings("unchecked") 
     T javaArray = (T) array.getArray(); 
     return javaArray; 
    } 

    @Override 
    public void nullSafeSet(PreparedStatement statement, Object value, int index, SessionImplementor session) 
      throws HibernateException, SQLException { 
     Connection connection = statement.getConnection(); 
     if (value == null) { 
      statement.setNull(index, SQL_TYPES[0]); 
     } else { 
      @SuppressWarnings("unchecked") 
      T castObject = (T) value; 
      Array array = connection.createArrayOf("integer", (Object[]) castObject); 
      statement.setArray(index, array); 
     } 
    } 

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

    @Override 
    public Class<T> returnedClass() { 
     return typeParameterClass; 
    } 

    @Override 
    public int[] sqlTypes() { 
     return new int[] { Types.ARRAY }; 
    } 


} 

そして配列プロパティは、同じ寸法のデータベースの同じタイプであろう:

  • integer[] - >Integer[]
  • text[][] - >String[][]

そして、この特殊なケースでプロパティの上にGenericTypeクラスを配置します

@Type(type = "packageofclass.GenericArrayUserType") 

次に、あなたのエンティティは次のようになります。

@Entity 
@Table(name="sal_emp") 
public class SalEmp { 

    @Id 
    private String name; 

    @Column(name="pay_by_quarter") 
    @Type(type = "packageofclass.GenericArrayUserType") 
    private Integer[] payByQuarter; 

    @Column(name="schedule") 
    @Type(type = "packageofclass.GenericArrayUserType") 
    private String[][] schedule; 

    //Getters, Setters, ToString, equals, and so on 

} 

あなたはこのジェネリックUserTypeInteger[]タイプを使用してString[][]タイプを書きたくない場合。あなたの場合のように次があるだろう、独自の型を記述する必要があります。

  • 整数[]

    public class IntArrayUserType implements UserType { 
    
    protected static final int[] SQL_TYPES = { Types.ARRAY }; 
    
    @Override 
    public Object assemble(Serializable cached, Object owner) throws HibernateException { 
        return this.deepCopy(cached); 
    } 
    
    @Override 
    public Object deepCopy(Object value) throws HibernateException { 
        return value; 
    } 
    
    @Override 
    public Serializable disassemble(Object value) throws HibernateException { 
        return (Integer[]) this.deepCopy(value); 
    } 
    
    @Override 
    public boolean equals(Object x, Object y) throws HibernateException { 
    
        if (x == null) { 
         return y == null; 
        } 
        return x.equals(y); 
    } 
    
    @Override 
    public int hashCode(Object x) throws HibernateException { 
        return x.hashCode(); 
    } 
    
    @Override 
    public boolean isMutable() { 
        return true; 
    } 
    
    @Override 
    public Object nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor session, Object owner) 
         throws HibernateException, SQLException { 
        if (resultSet.wasNull()) { 
         return null; 
        } 
        if (resultSet.getArray(names[0]) == null) { 
         return new Integer[0]; 
        } 
    
        Array array = resultSet.getArray(names[0]); 
        Integer[] javaArray = (Integer[]) array.getArray(); 
        return javaArray; 
    } 
    
    @Override 
    public void nullSafeSet(PreparedStatement statement, Object value, int index, SessionImplementor session) 
         throws HibernateException, SQLException { 
        Connection connection = statement.getConnection(); 
        if (value == null) { 
         statement.setNull(index, SQL_TYPES[0]); 
        } else { 
         Integer[] castObject = (Integer[]) value; 
         Array array = connection.createArrayOf("integer", castObject); 
         statement.setArray(index, array); 
        } 
    } 
    
    @Override 
    public Object replace(Object original, Object target, Object owner) throws HibernateException { 
        return original; 
    } 
    
    @Override 
    public Class<Integer[]> returnedClass() { 
        return Integer[].class; 
    } 
    
    @Override 
    public int[] sqlTypes() { 
        return new int[] { Types.ARRAY }; 
    } 
    } 
    
  • テキスト[] []

    public class StringMultidimensionalArrayType implements UserType { 
    
    protected static final int[] SQL_TYPES = { Types.ARRAY }; 
    
    @Override 
    public Object assemble(Serializable cached, Object owner) throws HibernateException { 
        return this.deepCopy(cached); 
    } 
    
    @Override 
    public Object deepCopy(Object value) throws HibernateException { 
        return value; 
    } 
    
    @Override 
    public Serializable disassemble(Object value) throws HibernateException { 
        return (String[][]) this.deepCopy(value); 
    } 
    
    @Override 
    public boolean equals(Object x, Object y) throws HibernateException { 
    
        if (x == null) { 
         return y == null; 
        } 
        return x.equals(y); 
    } 
    
    @Override 
    public int hashCode(Object x) throws HibernateException { 
        return x.hashCode(); 
    } 
    
    @Override 
    public boolean isMutable() { 
        return true; 
    } 
    
    @Override 
    public Object nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor session, Object owner) 
         throws HibernateException, SQLException { 
        if (resultSet.wasNull()) { 
         return null; 
        } 
        if (resultSet.getArray(names[0]) == null) { 
         return new String[0][]; 
        } 
    
        Array array = resultSet.getArray(names[0]); 
        String[][] javaArray = (String[][]) array.getArray(); 
        return javaArray; 
    } 
    
    @Override 
    public void nullSafeSet(PreparedStatement statement, Object value, int index, SessionImplementor session) 
         throws HibernateException, SQLException { 
        Connection connection = statement.getConnection(); 
        if (value == null) { 
         statement.setNull(index, SQL_TYPES[0]); 
        } else { 
         String[][] castObject = (String[][]) value; 
         Array array = connection.createArrayOf("integer", castObject); 
         statement.setArray(index, array); 
        } 
    } 
    
    @Override 
    public Object replace(Object original, Object target, Object owner) throws HibernateException { 
        return original; 
    } 
    
    @Override 
    public Class<String[][]> returnedClass() { 
        return String[][].class; 
    } 
    
    @Override 
    public int[] sqlTypes() { 
        return new int[] { Types.ARRAY }; 
    } 
    
    } 
    

この場合、プロパティにはさまざまな種類があります。

@Column(name="pay_by_quarter") 
@Type(type = "packageofclass.IntArrayUserType") 
private Integer[] payByQuarter; 

@Column(name="schedule") 
@Type(type = "packageofclass.StringMultidimensionalArrayType") 
private String[][] schedule; 

ブールまたはブール値で更新休止のUserType

それはGenericArrayUserTypeで動作しないので、解決策はタイプbyteaのごCREATE DDL宣言booleanに作成することができそうです:

CREATE TABLE sal_emp (
    name text, 
    pay_by_quarter integer[], 
    schedule  text[][], 
    wow_boolean  bytea 
    ); 

そして、あなたのタイプなしのプロパティ:

private boolean[][][] wowBoolean;

TypeまたはConverterがなくても非常に良いです。出力:JPA 2.1

@ConverterwowBoolean=[[[true, false], [true, false]], [[true, true], [true, true]]])

アップデート私はEclipseLinkHibernateでJPA 2.1の@Converterとオプションを試してみました。私はちょうどこの(* IがList<Integer>にプロパティを変更したが、それは問題ではありません)のようinteger[](ないtext[][]Converterを試してみた:

@Converter 
public class ConverterListInteger implements AttributeConverter<List<Integer>, Array>{ 

    @Override 
    public Array convertToDatabaseColumn(List<Integer> attribute) { 
     DataSource source = ApplicationContextHolder.getContext().getBean(DataSource.class); 

     try { 
      Connection conn = source.getConnection(); 
      Array array = conn.createArrayOf("integer", attribute.toArray()); 
      return array; 

     } catch (SQLException e) { 
      e.printStackTrace(); 
     } 

     return null; 

    } 

    @Override 
    public List<Integer> convertToEntityAttribute(Array dbData) { 
     List<Integer> list = new ArrayList<>(); 

     try { 
      for(Object object : (Object[]) dbData.getArray()){ 
       list.add((Integer) object); 
      } 
     } catch (SQLException e) { 
      e.printStackTrace(); 
     } 

     return list; 

    } 

} 

その後、エンティティ内のプロパティにコンバータを追加:

@Convert(converter=ConverterListInteger.class) 
private List<Integer> pay_by_quarter; 

したがって、JPA specificationに基づくソリューションは機能しません。どうして? Hibernateは

その後

私はのEclipseLink(hereを設定する方法を参照)を試したし、それが動作しますが、常にではない...バグがありますようだ、それが動作する....(java.sql.Array)を、データベースの配列をサポートしていません。最初はうまくいきますが、次回はこの行を更新またはクエリできません。ちょうど私が新しい行を追加し、成功しましたが、それは後に更新またはクエリすることはできません....

結論

現時点ではその後、適切にJPAベンダーがそこにサポートされていないようです... HibernateUserTypeのソリューションだけがうまくいきますが、それはちょうどHibernateです。

+0

ありがとうございます。休止状態に依存せずにこれを行う方法はありますか?例えば、 'org.hibernate.usertype.UserType'を使わないことは可能でしょうか?あるいは、このユースケースの唯一の解決策であるHibernateに依存していますか? – Zombies

+1

この解決方法は私が知っている '休止状態 'に依存しますが、それはより簡単だと思います。あなたが 'Hibernate'に依存しないことを望むなら、Jpa 2.1の '@ Converter'を使うことができます。この記事を参照してください:http://stackoverflow.com/a/24194996/4751165 – Pau

+0

JPA 2.1を扱う例では、 'Array'型、つまり' java.sql.Array'の権利を使用していますか? – harm

関連する問題