如何使用 Spring Data / JPA 插入 Postgres Array 类型列?
Posted
技术标签:
【中文标题】如何使用 Spring Data / JPA 插入 Postgres Array 类型列?【英文标题】:How to use Spring Data / JPA to insert into a Postgres Array type column? 【发布时间】:2016-12-31 08:15:44 【问题描述】:假设我有一个这样的 postgres 表:
CREATE TABLE sal_emp (
name text,
pay_by_quarter integer[],
schedule text[][]
);
我什至可以使用 Spring Data 插入列 pay_by_quarter
或 schedule
吗?如果可能的话,这看起来像 Repository 和 Entity 吗?我找不到任何解决此问题的文档或示例,可能是因为它与更常见的用例重叠,作为一对多关系插入到多个表中。说到这里,我完全打算使用 Postgresql array
数据类型并且没有关系表。
【问题讨论】:
是的,您可以使用 Spring Data 实现这一点。但是,我认为这是一个非常糟糕的做法,违反了 1NF (en.wikipedia.org/wiki/First_normal_form)。如果可能,请考虑修改模型.. @crm86 对于我要保存的特定数据(来自推特的大量推文),我更喜欢没有 FNF。我想将一对多的关系存储在一个表中(以避免对这种价值不高的数据进行如此多的连接)。我还想避免使用 NoSQL 文档数据库,因为它们对我来说难以维护。我想array
数据类型的另一种替代方法是JSONB
,并将所有内容作为文档存储在 postgresql 中。但我认为使用列(和一些数组数据类型)比使用 JSON(在 Postgresql 中)更好。
【参考方案1】:
您需要创建自己的类型并实现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;
更新休眠用户类型
使用布尔值或布尔值似乎不适用于GenericArrayUserType
,因此可以在您的CREATE DDL
声明boolean
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]]])
更新为@Converter
的JPA 2.1
我尝试了@Converter
of JPA 2.1 和EclipseLink
和Hibernate
的选项。我刚刚试过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;
然后,将转换器添加到Entity中的属性中:
@Convert(converter=ConverterListInteger.class)
private List<Integer> pay_by_quarter;
所以基于JPA specification
的解决方案不起作用。为什么? Hibernate 不支持数据库数组 (java.sql.Array
)....
然后我尝试使用 EclipseLink(请参阅如何配置 here)并且它可以工作,但并非总是如此......似乎有一个错误,它第一次运行良好,但下次无法更新或查询这一行。然后我就成功添加了新行,但是之后无法更新或查询....
结论
目前,JPA
供应商似乎没有正确支持...只有Hibernate
UserType
的解决方案效果很好,但它仅适用于Hibernate
。
【讨论】:
谢谢你。有没有办法在不依赖 Hibernate 的情况下做到这一点?例如,可以不使用org.hibernate.usertype.UserType
吗?还是依赖 Hibernate 是此用例的唯一解决方案?
这个解决方案取决于hibernate
我知道,但它认为更容易。如果你不想依赖“休眠”,你可以使用 Jpa 2.1 的@Converter
。看到这个帖子:***.com/a/24194996/4751165
在处理 JPA 2.1 的示例中,您使用 Array
类型,即 java.sql.Array
对吗?
您从未在第一个代码块中初始化 typeParameterClass。你没有得到空指针异常吗?
使用 GenericArrayUserType 时出现以下错误:“JDBC 类型没有方言映射:2003”【参考方案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;
【讨论】:
以上是关于如何使用 Spring Data / JPA 插入 Postgres Array 类型列?的主要内容,如果未能解决你的问题,请参考以下文章
如何将自定义sql附加到Spring Data JPA插入/更新
spring data jpa使用spring data jpa时,关于service层一个方法中进行删除和插入两种操作在同一个事务内处理