【问题标题】:How to map a PostgreSQL array with Hibernate如何使用 Hibernate 映射 PostgreSQL 数组
【发布时间】:2010-12-11 11:31:21
【问题描述】:

有没有人通过 Hibernate 成功地将 PostgreSQL 中的数值数组映射到 java 中的数值数组?

sql:

CREATE TABLE sal_emp (name text, pay_by_quarter integer[]);
INSERT INTO sal_emp VALUES ('one', '{1,2,3}');
INSERT INTO sal_emp VALUES ('two', '{4,5,6}');
INSERT INTO sal_emp VALUES ('three', '{2,4,6}');

映射:

<hibernate-mapping>
    <class name="SalEmp" table="sal_emp">
        <id name="name" />
        <property name="payByQuarter" column="pay_by_quarter" />
    </class>
</hibernate-mapping>

类:

public class SalEmp implements Serializable{
    private String name;
    private Integer[] payByQuarter;
    ...// getters & setters
}

查询表格时出现异常。

【问题讨论】:

    标签: java arrays postgresql hibernate orm


    【解决方案1】:

    Maven 依赖

    你需要做的第一件事是在你的项目pom.xml配置文件中设置如下Hibernate TypesMaven依赖:

    <dependency>
        <groupId>com.vladmihalcea</groupId>
        <artifactId>hibernate-types-52</artifactId>
        <version>${hibernate-types.version}</version>
    </dependency>
    

    假设你的数据库中有这个表:

    create table event (
        id int8 not null, 
        version int4, 
        sensor_names text[], 
        sensor_values integer[], 
        primary key (id)
    )
    

    你想像这样映射它:

    @Entity(name = "Event")
    @Table(name = "event")
    @TypeDefs({
        @TypeDef(
            name = "string-array", 
            typeClass = StringArrayType.class
        ),
        @TypeDef(
            name = "int-array", 
            typeClass = IntArrayType.class
        )
    })
    public static class Event extends BaseEntity {
     
        @Type( type = "string-array" )
        @Column(
            name = "sensor_names", 
            columnDefinition = "text[]"
        )
        private String[] sensorNames;
     
        @Type( type = "int-array" )
        @Column(
            name = "sensor_values", 
            columnDefinition = "integer[]"
        )
        private int[] sensorValues;
     
        //Getters and setters omitted for brevity
    }
    

    string-arrayint-array 是自定义类型,可以在 BaseEntity 超类中定义:

    @TypeDefs({
        @TypeDef(
            name = "string-array", 
            typeClass = StringArrayType.class
        ),
        @TypeDef(
            name = "int-array", 
            typeClass = IntArrayType.class
        )
    })
    @MappedSuperclass
    public class BaseEntity {
    
        @Id
        private Long id;
    
        @Version
        private Integer version;
    
        //Getters and setters omitted for brevity
    }
    

    StringArrayTypeIntArrayType 是 Hibernate Types 项目提供的类。

    测试时间

    现在,当您插入几个实体时;

    Event nullEvent = new Event();
    nullEvent.setId(0L);
    entityManager.persist(nullEvent);
     
    Event event = new Event();
    event.setId(1L);
    event.setSensorNames(
        new String[] {
            "Temperature", 
            "Pressure"
        }
    );
    event.setSensorValues( 
        new int[] {
            12, 
            756
        } 
    );
    entityManager.persist(event);
    

    Hibernate 将生成以下 SQL 语句:

    INSERT INTO event (
        version, 
        sensor_names, 
        sensor_values, 
        id
    ) 
    VALUES (
        0, 
        NULL(ARRAY), 
        NULL(ARRAY), 
        0
    )
         
    INSERT INTO event (
        version, 
        sensor_names, 
        sensor_values, 
        id
    ) 
    VALUES ( 
        0, 
        {"Temperature","Pressure"}, 
        {"12","756"}, 
        1
    )
    

    【讨论】:

    • 与 UserType 相比,DynamicParameterizedType 有什么优势?
    • 它允许您将实体属性信息传递给 Hibernate 类型。
    • 好的。是否可以使用 List 数据类型而不是数组数据类型?
    • 我复制了您的代码 vladmihalcea.com/… 并在 abstractArrayTypeDescriptor.getSqlArrayType() 上收到错误以获取受保护的访问。我使用最新版本的库 'com.vladmihalcea',名称:'hibernate-types-52',版本:'2.2.0'
    • 在示例代码中,您将 JavaTypeDescriptor 转换为 AbstractArrayTypeDescriptor 并且您拥有“受保护的抽象字符串 getSqlArrayType();”在课堂上。
    【解决方案2】:

    Hibernate 不支持开箱即用的数据库数组(例如映射到 java.sql.Array 的数组)。

    Hibernate 提供的arrayprimitive-array 类型用于将Java 数组映射到后备表——它们基本上是一对多/元素集合映射的变体,所以这不是你想要的。

    不过,最新的 PostgreSQL JDBC 驱动程序 (8.4.whatever) 支持 JDBC4 Connection.createArrayOf() 方法以及 ResultSet.getArray()PreparedStatement.setArray() 方法,因此您可以编写自己的 UserType 来提供数组支持。

    Here 是一个处理 Oracle 数组的 UserType 实现,它提供了一个很好的起点,改用它来处理 java.sql.Array 相当简单。

    【讨论】:

    【解决方案3】:

    也许这对其他人有用:我发现在我的情况下它表现不佳并且不能与 c3p0 一起使用。 (只是简单探讨了这些问题,是否可以解决请指正!)

    休眠 3.6:

    import java.io.Serializable;
    import java.sql.Array;
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.Arrays;
    
    import org.apache.commons.lang.ArrayUtils;
    import org.hibernate.HibernateException;
    import org.hibernate.usertype.UserType;
    
    public class IntArrayUserType implements UserType {
    protected static final int  SQLTYPE = java.sql.Types.ARRAY;
    
    @Override
    public Object nullSafeGet(final ResultSet rs, final String[] names, final Object owner) throws HibernateException, SQLException {
        Array array = rs.getArray(names[0]);
        Integer[] javaArray = (Integer[]) array.getArray();
        return ArrayUtils.toPrimitive(javaArray);
    }
    
    @Override
    public void nullSafeSet(final PreparedStatement statement, final Object object, final int i) throws HibernateException, SQLException {
        Connection connection = statement.getConnection();
    
        int[] castObject = (int[]) object;
        Integer[] integers = ArrayUtils.toObject(castObject);
        Array array = connection.createArrayOf("integer", integers);
    
        statement.setArray(i, array);
    }
    
    @Override
    public Object assemble(final Serializable cached, final Object owner) throws HibernateException {
        return cached;
    }
    
    @Override
    public Object deepCopy(final Object o) throws HibernateException {
        return o == null ? null : ((int[]) o).clone();
    }
    
    @Override
    public Serializable disassemble(final Object o) throws HibernateException {
        return (Serializable) o;
    }
    
    @Override
    public boolean equals(final Object x, final Object y) throws HibernateException {
        return x == null ? y == null : x.equals(y);
    }
    
    @Override
    public int hashCode(final Object o) throws HibernateException {
        return o == null ? 0 : o.hashCode();
    }
    
    @Override
    public boolean isMutable() {
        return false;
    }
    
    @Override
    public Object replace(final Object original, final Object target, final Object owner) throws HibernateException {
        return original;
    }
    
    @Override
    public Class<int[]> returnedClass() {
        return int[].class;
    }
    
    @Override
    public int[] sqlTypes() {
        return new int[] { SQLTYPE };
    }
    }
    

    【讨论】:

    • 如果int[]null,则nullSafeGetnullSafeSet 不起作用。您需要检查空值。
    【解决方案4】:

    这已经针对字符串数组进行了测试。数字数组可能需要对转换器进行一些修改。这适用于 Spring JPA。

    1) 将PostgreSQLTextArray 添加到您的项目中

    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.Arrays;
    import java.util.Map;
    
    /**
     * This is class provides {@link java.sql.Array} interface for PostgreSQL <code>text</code> array.
     *
     * @author Valentine Gogichashvili
     *
     */
    
    public class PostgreSQLTextArray implements java.sql.Array {
    
        private final String[] stringArray;
        private final String stringValue;
    
        /**
         * Initializing constructor
         * @param stringArray
         */
        public PostgreSQLTextArray(String[] stringArray) {
            this.stringArray = stringArray;
            this.stringValue = stringArrayToPostgreSQLTextArray(this.stringArray);
    
        }
    
        @Override
        public String toString() {
            return stringValue;
        }
    
        private static final String NULL = "NULL";
    
        /**
         * This static method can be used to convert an string array to string representation of PostgreSQL text array.
         * @param a source String array
         * @return string representation of a given text array
         */
        public static String stringArrayToPostgreSQLTextArray(String[] stringArray) {
            final int arrayLength;
            if ( stringArray == null ) {
                return NULL;
            } else if ( ( arrayLength = stringArray.length ) == 0 ) {
                return "{}";
            }
            // count the string length and if need to quote
            int neededBufferLentgh = 2; // count the beginning '{' and the ending '}' brackets
            boolean[] shouldQuoteArray = new boolean[stringArray.length];
            for (int si = 0; si < arrayLength; si++) {
                // count the comma after the first element
                if ( si > 0 )  neededBufferLentgh++;
    
                boolean shouldQuote;
                final String s = stringArray[si];
                if ( s == null ) {
                    neededBufferLentgh += 4;
                    shouldQuote = false;
                } else {
                    final int l = s.length();
                    neededBufferLentgh += l;
                    if ( l == 0 || s.equalsIgnoreCase(NULL) ) {
                        shouldQuote = true;
                    } else {
                        shouldQuote = false;
                        // scan for commas and quotes
                        for (int i = 0; i < l; i++) {
                            final char ch = s.charAt(i);
                            switch(ch) {
                                case '"':
                                case '\\':
                                    shouldQuote = true;
                                    // we will escape these characters
                                    neededBufferLentgh++;
                                    break;
                                case ',':
                                case '\'':
                                case '{':
                                case '}':
                                    shouldQuote = true;
                                    break;
                                default:
                                    if ( Character.isWhitespace(ch) ) {
                                        shouldQuote = true;
                                    }
                                    break;
                            }
                        }
                    }
                    // count the quotes
                    if ( shouldQuote ) neededBufferLentgh += 2;
                }
                shouldQuoteArray[si] = shouldQuote;
            }
    
            // construct the String
            final StringBuilder sb = new StringBuilder(neededBufferLentgh);
            sb.append('{');
            for (int si = 0; si < arrayLength; si++) {
                final String s = stringArray[si];
                if ( si > 0 ) sb.append(',');
                if ( s == null ) {
                    sb.append(NULL);
                } else {
                    final boolean shouldQuote = shouldQuoteArray[si];
                    if ( shouldQuote ) sb.append('"');
                    for (int i = 0, l = s.length(); i < l; i++) {
                        final char ch = s.charAt(i);
                        if ( ch == '"' || ch == '\\' ) sb.append('\\');
                        sb.append(ch);
                    }
                    if ( shouldQuote ) sb.append('"');
                }
            }
            sb.append('}');
            assert sb.length() == neededBufferLentgh;
            return sb.toString();
        }
    
    
        @Override
        public Object getArray() throws SQLException {
            return stringArray == null ? null : Arrays.copyOf(stringArray, stringArray.length);
        }
    
        @Override
        public Object getArray(Map<String, Class<?>> map) throws SQLException {
            return getArray();
        }
    
        @Override
        public Object getArray(long index, int count) throws SQLException {
            return stringArray == null ? null : Arrays.copyOfRange(stringArray, (int) index, (int) index + count);
        }
    
        @Override
        public Object getArray(long index, int count, Map<String, Class<?>> map) throws SQLException {
            return getArray(index, count);
        }
    
        @Override
        public int getBaseType() throws SQLException {
            return java.sql.Types.VARCHAR;
        }
    
        @Override
        public String getBaseTypeName() throws SQLException {
            return "text";
        }
    
        @Override
        public ResultSet getResultSet() throws SQLException {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public ResultSet getResultSet(Map<String, Class<?>> map) throws SQLException {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public ResultSet getResultSet(long index, int count) throws SQLException {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public ResultSet getResultSet(long index, int count, Map<String, Class<?>> map) throws SQLException {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public void free() throws SQLException {
        }
    
    }
    

    2) 将ListToArrayConverter 添加到您的代码中

    import org.postgresql.jdbc4.Jdbc4Array;
    
    import javax.persistence.AttributeConverter;
    import javax.persistence.Converter;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.List;
    
    @Converter(autoApply = true)
    public class ListToArrayConveter implements AttributeConverter<List<String>, Object> {
        @Override
        public PostgreSQLTextArray convertToDatabaseColumn(List<String> attribute) {
            if (attribute == null || attribute.isEmpty()) {
                return null;
            }
            String[] rst = new String[attribute.size()];
            return new PostgreSQLTextArray(attribute.toArray(rst));
        }
    
        @Override
        public List<String> convertToEntityAttribute(Object dbData) {
    
            List<String> rst = new ArrayList<>();
            try {
                String[] elements = (String[]) ((Jdbc4Array) dbData).getArray();
                for (String element : elements) {
                    rst.add(element);
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
    
    
            return rst;
        }
    }
    

    3) 使用它!

    @Entity
    @Table(name = "emails")
    public class Email {
    
        [...]
    
        @SuppressWarnings("JpaAttributeTypeInspection")
        @Column(name = "subject", columnDefinition = "text[]")
        @Convert(converter = ListToArrayConveter.class)
        private List<String> subject;
    
        [...]
    

    【讨论】:

    • 感谢您的代码。注意:我必须将 '@Converter(autoApply = true)' 替换为 '@Converter' 才能使其工作 - 我遇到了这个问题:stackoverflow.com/q/34767619/4828060
    【解决方案5】:

    我能够通过 here 发布的 JPA 转换器方法将 String[] 保存到 PostgreSQL 9.4 和 EclipseLink 2.6.2

    这似乎是答案的来源

    2016 年 7 月 1 日的 Tk421。

    从数据库加载数组也很有效。

    另外添加到 persistence.xml 我的 ListToArrayConverter 路径:

    <class> com.foo1.foo2.foo3.backend.jpa.convert.ListToArrayConverter </class>
    

    请提及 Jdbc4Array 不再出现在 Postgre JDBC 驱动程序中,请改用:

    org.postgresql.jdbc.PgArray
    

    请看这里: Package org.postgresql.jdbc4 is missing since 9.4-1207

    【讨论】:

      【解决方案6】:

      这里是 int[] UserType 我用来做你所追求的,它还包括对 nullSafeGet()nullSafeSet() 的空检查:

      import org.apache.commons.lang.ArrayUtils;
      import org.hibernate.HibernateException;
      import org.hibernate.engine.spi.SessionImplementor;
      import org.hibernate.usertype.UserType;
      
      import java.io.Serializable;
      import java.sql.Array;
      import java.sql.Connection;
      import java.sql.PreparedStatement;
      import java.sql.ResultSet;
      import java.sql.SQLException;
      
      public class IntegerArrayUserType implements UserType {
          protected static final int  SQLTYPE = java.sql.Types.ARRAY;
      
          @Override
          public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException {
              Array array = rs.getArray(names[0]);
              if (array == null) {
                  return null;
              }
              Integer[] javaArray = (Integer[]) array.getArray();
              return ArrayUtils.toPrimitive(javaArray);
          }
      
          @Override
          public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
              Connection connection = st.getConnection();
      
              if (value == null) {
                  st.setNull( index, sqlTypes()[0] );
              } else {
                  int[] castObject = (int[]) value;
                  Integer[] integers = ArrayUtils.toObject(castObject);
                  Array array = connection.createArrayOf("integer", integers);
      
                  st.setArray(index, array);
              }
          }
      
          @Override
          public Object assemble(final Serializable cached, final Object owner) throws HibernateException {
              return cached;
          }
      
          @Override
          public Object deepCopy(final Object o) throws HibernateException {
              return o == null ? null : ((int[]) o).clone();
          }
      
          @Override
          public Serializable disassemble(final Object o) throws HibernateException {
              return (Serializable) o;
          }
      
          @Override
          public boolean equals(final Object x, final Object y) throws HibernateException {
              return x == null ? y == null : x.equals(y);
          }
      
          @Override
          public int hashCode(final Object o) throws HibernateException {
              return o == null ? 0 : o.hashCode();
          }
      
          @Override
          public boolean isMutable() {
              return false;
          }
      
          @Override
          public Object replace(final Object original, final Object target, final Object owner) throws HibernateException {
              return original;
          }
      
          @Override
          public Class<int[]> returnedClass() {
              return int[].class;
          }
      
          @Override
          public int[] sqlTypes() {
              return new int[] { SQLTYPE };
          }
      }
      

      【讨论】:

      • 你是怎么用的?
      猜你喜欢
      • 2017-10-29
      • 1970-01-01
      • 2015-03-09
      • 2011-02-17
      • 2011-05-18
      • 2023-03-20
      • 2013-05-03
      • 1970-01-01
      • 2020-01-06
      相关资源
      最近更新 更多