Java 使用 Hibernate 映射 PostgreSQL 数组

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1647583/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 18:39:17  来源:igfitidea点击:

Mapping a PostgreSQL array with Hibernate

javaarrayshibernatepostgresql

提问by pstanton

has anyone successfully mapped a numeric array in PostgreSQL to a numeric array in java via Hibernate?

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

sql:

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}');

mapping:

映射:

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

class:

班级:

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

i get an exception when querying the table.

查询表时出现异常。

采纳答案by ChssPly76

Hibernate does not support database arrays (e.g. ones mapped to java.sql.Array) out of the box.

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

arrayand primitive-arraytypes provided by Hibernate are for mapping Java arrays into backing table - they're basically a variation of one-to-many / collection-of-elements mappings, so that's not what you want.

arrayprimitive-arrayHibernate提供类型映射Java数组到支撑台-它们基本上是一个一对多/收集元素的的映射的变化,所以这不是你想要的。

Latest PostgreSQL JDBC driver (8.4.whatever) supports JDBC4 Connection.createArrayOf()method as well as ResultSet.getArray()and PreparedStatement.setArray()methods, though, so you can write your own UserTypeto provide array support.

最新的PostgreSQL的JDBC驱动程序(8.4.whatever)支持JDBC4Connection.createArrayOf()方法以及ResultSet.getArray()PreparedStatement.setArray()方法,虽然如此,你可以写你自己UserType提供的阵列支持。

Hereis a UserType implementation dealing with Oracle array that provides a good starting point, it's reasonably straightforward to adapt it to handle java.sql.Arrayinstead.

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

回答by Tim

Perhaps this is useful for someone else: I found that in my case it performs poorly and could not be used with c3p0. (Only explored these issues briefly, is they can be resolved please correct me!)

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

Hibernate 3.6:

休眠 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 };
}
}

回答by Tk421

This has been tested against string arrays. Maybe some modifications in the converter is required for numeric arrays. This works with Spring JPA.

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

1) add PostgreSQLTextArrayto your project

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) Add ListToArrayConverterto your code

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) Use it!

3)使用它!

@Entity
@Table(name = "emails")
public class Email {

    [...]

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

    [...]

回答by Shane Rowatt

Here is the int[]UserType I used to do what you're after which also includes the null checks for nullSafeGet()and nullSafeSet():

这是int[]我用来执行您要执行的操作的UserType,其中还包括对nullSafeGet()and的空检查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 };
    }
}

回答by kitekat

I was able to save a String[]to PostgreSQL 9.4 and EclipseLink 2.6.2 via the JPA Converter approach posted here

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

which seems to be the source for the answer of

这似乎是答案的来源

Tk421 of 1st July 2016.

2016 年 7 月 1 日的 Tk421。

Loading an Array from DB also works well.

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

Additionally added to persistence.xml:

另外添加到persistence.xml

<class> com.ssg.fcp.fcp_e761.therealthing.backend.jpa.convert.ListToArrayConverter </class>

Please mention that Jdbc4Arrayis not present in Postgre JDBC driver anymore, please instead use:

请注意Jdbc4ArrayPostgre JDBC 驱动程序中不再存在,请改用:

org.postgresql.jdbc.PgArray

See here: Package org.postgresql.jdbc4 is missing since 9.4-1207

请参阅此处: 自 9.4-1207 以来,缺少包 org.postgresql.jdbc4

回答by Vlad Mihalcea

This is a very common question, so I decided to turn this answer into a detailed article.

这是一个很常见的问题,所以我决定把这个答案变成一篇详细的文章

Maven dependency

Maven 依赖

The first thing you need to do is to set up the following Hibernate TypesMaven dependency in your project pom.xmlconfiguration file:

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

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

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-arrayand int-arrayare custom types which can be defined in the BaseEntitysuperclass:

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
}

The StringArrayTypeand IntArrayTypeare classes offered by the Hibernate Types project.

StringArrayTypeIntArrayType由Hibernate的类型项目提供的课程。

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:

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
)