Java 如何使用SpringData/JPA插入到Postgres数组类型列中?

Java 如何使用SpringData/JPA插入到Postgres数组类型列中?,java,postgresql,spring-data,spring-data-jpa,Java,Postgresql,Spring Data,Spring Data Jpa,假设我有一张postgres表,如下所示: CREATE TABLE sal_emp ( name text, pay_by_quarter integer[], schedule text[][] ); 我甚至可以使用Spring数据插入列pay\u by\u quarty或schedule?如果可能的话,它作为一个存储库和实体会是什么样子?我还没有找到任何文档或示例来解决这个问题,可能是因为它与更常见的用例重叠,以一对多关系插入

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

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

我甚至可以使用Spring数据插入列
pay\u by\u quarty
schedule
?如果可能的话,它作为一个存储库和实体会是什么样子?我还没有找到任何文档或示例来解决这个问题,可能是因为它与更常见的用例重叠,以一对多关系插入多个表中。说到这里,我完全打算使用Postgresql
数组
数据类型,而不使用关系表。

您需要创建自己的类型并实现
用户类型接口
。基于next,我编写了一个通用的
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 };
    }


}
那么您的实体将是:

@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

}
如果您不想使用此通用的
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 };
    }
    }
    
    更新Hibernate用户类型 对于布尔型或布尔型,它似乎不适用于
    GenericArrayUserType
    ,因此可以在
    create DDL
    declare
    Boolean
    类型的
    bytea
    中创建解决方案:

    CREATE TABLE sal_emp (
        name text,
        pay_by_quarter  integer[],
        schedule        text[][],
        wow_boolean     bytea
        );
    
    以及您的财产(不含任何类型):

    private boolean[]wowBoolean

    它的解析非常好,没有任何
    类型
    转换器
    。输出:
    wowBoolean=[[[true,false],[true,false]],[[true,true],[true,true]]])

    使用
    JPA 2.1的
    @Converter
    更新

    我用JPA2.1的
    @Converter
    eclipseelink
    Hibernate
    尝试了一个选项。我刚刚尝试过这样的
    integer[]
    (而不是
    text[][]
    Converter
    (*我已将属性更改为
    列表
    ),但这并不重要):

    @转换器
    公共类ConverterListenter实现AttributeConverter{
    @凌驾
    公共数组convertToDatabaseColumn(列表属性){
    DataSource source=ApplicationContextHolder.getContext().getBean(DataSource.class);
    试一试{
    连接conn=source.getConnection();
    Array Array=conn.createArrayOf(“integer”,attribute.toArray());
    返回数组;
    }捕获(SQLE异常){
    e、 printStackTrace();
    }
    返回null;
    }
    @凌驾
    公共列表convertToEntityAttribute(数组dbData){
    列表=新的ArrayList();
    试一试{
    对于(对象对象:(对象[])dbData.getArray()){
    添加((整数)对象);
    }
    }捕获(SQLE异常){
    e、 printStackTrace();
    }
    退货清单;
    }
    }
    
    然后,将转换器添加到实体中的属性:

    @Convert(converter=ConverterListInteger.class)
    private List<Integer> pay_by_quarter;
    
    @Convert(converter=converterlistineger.class)
    私人名单按季度支付;
    
    因此,基于
    JPA规范的解决方案不起作用。为什么?Hibernate不支持数据库数组(
    java.sql.Array

    然后我尝试了EclipseLink(请参阅如何配置),它可以工作,但并不总是……似乎有一个bug,它第一次工作得很好,但下一次无法更新或查询此行。然后我成功地添加了新行,但之后无法更新或查询

    结论
    目前,
    JPA
    供应商似乎不支持。。。只有使用
    Hibernate
    UserType
    的解决方案才能很好地工作,但它仅适用于
    Hibernate

    简单的方法将是

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

    @Column(name=“nice\u 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数据实现这一点。但是,我认为这是一种非常糟糕的做法,违反了1NF()。如果可能的话,考虑模型修订。@ CRM86-我宁愿没有FNF用于特定的数据我正在保存(从推特推特的大量流)。我希望将一对多关系存储在一个表中(以避免对这些低值数据进行如此多的联接)。我还想避免使用NoSQL文档数据库,因为它们难以维护。我假设
    array
    数据类型的另一种替代方法是
    JSONB
    ,并将所有内容作为文档存储在postgresql中。但是我认为使用列(和一些数组数据类型)比使用JSON(在Postgresql中)要好。谢谢。有没有不依赖Hibernate的方法来做到这一点??例如,是否可以不使用
    org.hibernate.usertype.usertype
    ??或者依赖Hibernate是这个用例的唯一解决方案吗?这个解决方案依赖于
    Hibernate
    ,我知道,但它认为更简单。如果您不想依赖“Hibernate”,可以使用JPA2.1的
    @Converter
    。请参阅本文:在处理JPA2.1的示例中,您使用了
    Array
    类型,即
    java.sql.Array
    对吗?您从未在第一个代码块中初始化过typeParameterClass。您没有得到空指针异常吗?我使用GenericarArrayUserType得到以下错误:“没有JDBC类型的方言映射:2003”
    CREATE TABLE sal_emp (
        name text,
        pay_by_quarter  integer[],
        schedule        text[][],
        wow_boolean     bytea
        );
    
    @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;
    
    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;
        }