web-dev-qa-db-ja.com

Spring Data / JPAを使用してPostgres配列型の列に挿入する方法

次のようなpostgresテーブルがあるとします。

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

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

12
Zombies

独自のタイプを作成し、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

}

このGeneric UserTypeを使用しない場合は、Integer[]タイプを使用して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;

Hibernate UserTypeを更新

ブール値またはブール値ではGenericArrayUserTypeでは機能しないため、ソリューションをCREATE DDL宣言booleanofタイプbyteaで作成できます。

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

そして、タイプのないあなたの財産:

private boolean[][][] wowBoolean;

TypeまたはConverterがなくても非常に良好に解析されます。出力:wowBoolean=[[[true, false], [true, false]], [[true, true], [true, true]]])

@ConverterJPA 2.1で更新

EclipseLinkおよびHibernateを使用して、JPA 2.1の@Converterでオプションを試しました。私はinteger[]text[][]ではなく)Converterをこのように試しました(*プロパティをList<Integer>に変更しましたが、重要ではありません)。

@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はデータベース配列(Java.sql.Array)....をサポートしていません。

それから私はEclipseLinkを試してみました(設定方法を参照してください ここ を参照してください)そしてそれはうまくいくとは限りません...バグがあるようです、それは最初はうまくいきますが、次回はそれが不可能ですこの行を更新または照会します。それから私は成功して新しい行を追加しましたが、後で更新したりクエリしたりすることはできません...

結論

現時点では、JPAベンダーによって適切にサポートされていないようです... HibernateUserTypeを使用したソリューションのみが適切に機能しますが、Hibernateのみを対象としています。

17
Pau