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

假设我有一个像这样的postgres表:

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

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

您需要创建自己的类型并实现UserType interface 。 基于下一个响应,我已经编写了一个Generic UserType ,可以在所有数组中使用它,但它必须使用非原始数据类型 (Integer,Long,String,…)。 否则请使用Boolean类型查看上述更新。

 public class GenericArrayUserType implements UserType { protected static final int[] SQL_TYPES = { Types.ARRAY }; private Class 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 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 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 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 ,因此可以在类型为bytea CREATE DDL声明boolean中创建解决方案:

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

而你的财产没有任何类型:

private boolean[][][] wowBoolean;

它解析非常好,没有任何TypeConverter 。 输出: wowBoolean=[[[true, false], [true, false]], [[true, true], [true, true]]])

使用JPA 2.1 @Converter进行更新

我已经尝试使用带有EclipseLinkHibernate的JPA 2.1的@Converter选项。 我刚试过integer[] (不是text[][] )这样的Converter (*我已经将属性更改为List但是没关系):

 @Converter public class ConverterListInteger implements AttributeConverter, Array>{ @Override public Array convertToDatabaseColumn(List 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 convertToEntityAttribute(Array dbData) { List 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 pay_by_quarter; 

所以基于JPA specification的解决方案不起作用。 为什么? Hibernate不支持数据库数组( java.sql.Array )….

然后我尝试使用EclipseLink(请参阅如何在此处配置)并且它可以正常工作,但并非总是如此……似乎有一个错误,它第一次运行良好,但接下来无法更新或查询此行。 然后我只是成功添加新行但是之后无法更新或查询….

结论

目前, JPA供应商似乎没有正确支持……只有Hibernate UserType的解决方案才能正常运行,但它只适用于Hibernate