Mapping array with Hibernate

后端 未结 4 811
谎友^
谎友^ 2020-11-29 09:53

Can you please help me to map this class using Hibernate?

public class MyClass{
    private Long id;
    private String name;
    private int[] values;
    .         


        
相关标签:
4条回答
  • 2020-11-29 10:00

    This is a very good question, so I decided to write this article to explain how you can achieve this goal using the Hibernate Types project.

    Maven dependency

    The first thing you need to do is to set up the following Hibernate Types Maven dependency in your project pom.xml configuration file:

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

    Maven ARRAY columns

    Assuming you have this table in your database:

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

    And you want to map it like this:

    @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
    }
    

    The string-array and int-array are custom types which can be defined in the BaseEntity superclass:

    @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
    }
    

    The StringArrayType and IntArrayType are classes offered by the Hibernate Types project.

    Testing time

    Now, when you insert a couple of entities;

    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 is going to generate the following SQL statements:

    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
    )
    
    0 讨论(0)
  • 2020-11-29 10:11

    I have never mapped arrays to hibernate. I always use collections. So, I have slightly changed you class:

    public class MyClass{
        private Long id;
        private String name;
        private List<Integer> values;
    
        @Id
        // this is only if your id is really auto generated
        @GeneratedValue(strategy=GenerationType.AUTO) 
        public Long getId() {
            return id;
        }
    
        @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
        public List<Integer> getValues() {
            return values;
        }   
        ...
    
    0 讨论(0)
  • 2020-11-29 10:24

    Hibernate (and JPA) can't directly map the PostgreSQL array type. See this question for how to proceed if you really need to retain your database structure as it is. This thread has an example of the required custom type.

    If you can change your schema, you can let hibernate create an additional table to handle the collection - List<Integer>. Then, depending on the version of hibernate you are using:

    • JPA 2.0 compliant - use @ElementCollection
    • JPA 1.0 compliant - use @CollectionOfElements
    0 讨论(0)
  • 2020-11-29 10:24

    Hibernate can map only the primitive types. Check under the org.hibernate.type folder of hibernate jar package. int array is not one of them. So you would have to write a custom type that can implement the UserType interface.

    public class MyClass{
         private Long id;
         private String name;
         private Integer[] values;
    
         @Type(type = "com.usertype.IntArrayUserType")
         public Integer[] getValues(){
             return values;
         }
    
         public void setValues(Integer[] values){
             this.values = values;
         }
     }
    

    IntArrayUserType.class

    package com.usertype.IntArrayUserType;
    
    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 };
    }
    

    When you query for the MyClass entity you can add something like this:

    Type intArrayType = new TypeLocatorImpl(new TypeResolver()).custom(IntArrayUserType.class);
    Query query = getSession().createSQLQuery("select values from MyClass")
       .addScalar("values", intArrayType);
    List<Integer[]> results = (List<Integer[]>) query.list();
    
    0 讨论(0)
提交回复
热议问题