如何使用Spring Data / JPA插入Postgres Array类型列?

假设我有一个 postgres 表,如下所示:

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

我甚至可以使用Spring Data插入到列中吗?如果可能的话,作为存储库和实体,这将是什么样子?我无法找到任何文档或示例来解决这个问题,可能是因为它与更常见的用例重叠,作为一对多关系插入到多个表中。说到这一点,我完全打算使用Postgresql数据类型,而不是关系表。pay_by_quarterschedulearray


答案 1

您需要创建自己的类型并实现 .基于下一个响应,我编写了一个通用型以用于所有数组,它可以工作,但您必须使用非基元数据类型(整数,长整型,字符串,...)。否则,请参阅上面的类型更新。UserType interfaceUserTypeBoolean

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;

更新休眠用户类型

使用布尔值或布尔值似乎它不适用于 ,因此可以在类型声明中创建解决方案:GenericArrayUserTypeCREATE DDLbooleanbytea

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

以及您的房产,没有任何类型:

private boolean[][][] wowBoolean;

它解析得很好,没有任何或.输出:TypeConverterwowBoolean=[[[true, false], [true, false]], [[true, true], [true, true]]])

使用 JPA 2.1 @Converter进行更新

我尝试了带有和的JPA 2.1选项。我刚刚尝试过(不是)像这样(*我已将属性更改为a,但这并不重要):@ConverterEclipseLinkHibernateinteger[]text[][]ConverterList<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 specificationjava.sql.Array

然后我尝试了EclipseLink(请参阅如何在此处配置),并且它有效,但并非总是...似乎有一个错误,它第一次工作得很好,但下一次无法更新或查询此行。然后,我只是成功添加了新行,但之后无法更新或查询....

结论

目前,似乎没有供应商正确支持...只有 解决方案才能很好地工作,但它只适用于 .JPAHibernateUserTypeHibernate


答案 2

简单的方法将是

尝试将字符串[]转换为字符串,然后在实体类中使

@Column(name = "nice_work" columnDefinition="text")

用于将字符串[]转换为字符串的函数,反之亦然

private static String stringArrayTOString(String[] input) {
        StringBuffer sb =new StringBuffer("");
        int i=0;
        for(String value:input) {
            
            if(i!=0) {
                sb.append(",");
            }
            sb.append(value);
            i++;
        }
        return sb.toString();
    }
    
    private static String[] stringToStringArray(String input) {
        String[] output = input.split(",");
        return output;
    }

推荐