2009-10-30 37 views
28

có ai lập bản đồ thành công một mảng số trong PostgreSQL tới một mảng số trong java qua Hibernate không?Lập bản đồ mảng PostgreSQL với Hibernate

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

lập bản đồ:

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

lớp:

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

tôi nhận được một ngoại lệ khi truy vấn bàn.

Trả lời

22

Hibernate không hỗ trợ mảng cơ sở dữ liệu (ví dụ: các bản đồ được ánh xạ tới java.sql.Array) ra khỏi hộp.

arrayprimitive-array loại được cung cấp bởi Hibernate là để ánh xạ mảng Java vào bảng sao lưu - về cơ bản chúng là biến thể của ánh xạ từ một đến nhiều bộ sưu tập, vì vậy đó không phải là những gì bạn muốn.

mới nhất lái xe PostgreSQL JDBC (8.4.whatever) hỗ trợ JDBC4 Connection.createArrayOf() phương pháp cũng như ResultSet.getArray()PreparedStatement.setArray() phương pháp, tuy nhiên, do đó bạn có thể viết riêng bạn UserType để cung cấp hỗ trợ mảng.

Here là triển khai UserType đối phó với mảng Oracle cung cấp điểm khởi đầu tốt, việc thay đổi nó phù hợp để xử lý java.sql.Array thay thế một cách hợp lý.

+12

Trong trường hợp bất cứ ai khác đã tự hỏi những gì liên kết cuối cùng là về (nó bây giờ 404), liên kết máy Wayback là ở đây - http : //web.archive.org/web/20090325101739/http: //www.hibernate.org/393.html – Sam

7

Có lẽ điều này rất hữu ích cho người khác: Tôi thấy rằng trong trường hợp của tôi nó hoạt động kém và không thể được sử dụng với c3p0. (Chỉ khám phá những vấn đề này một thời gian ngắn, là họ có thể được giải quyết hãy sửa lại cho tôi!)

Hibernate 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 }; 
} 
} 
+3

'nullSafeGet' và' nullSafeSet' không hoạt động nếu 'int []' là 'null'. Bạn cần phải kiểm tra các giá trị null. –

3

này đã được thử nghiệm chống lại mảng chuỗi. Có lẽ một số sửa đổi trong bộ chuyển đổi là bắt buộc đối với mảng số. Điều này làm việc với Spring JPA.

1) thêm PostgreSQLTextArray để dự án của bạn

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) Thêm ListToArrayConverter mã của bạn

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) Sử dụng nó!

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

    [...] 

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

    [...] 
1

Đây là loại người dùng int[] tôi sử dụng để làm những gì bạn đang sau đó cũng bao gồm việc kiểm tra null cho 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 }; 
    } 
} 
3

tôi đã có thể tiết kiệm rất String[] để PostgreSQL 9.4 và EclipseLink 2.6.2 thông qua cách tiếp cận JPA Chuyển đổi posted here

mà dường như là nguồn cho câu trả lời của

Tk421 của ngày 1 tháng 7 năm 2016.

tải một mảng từ DB cũng hoạt động tốt.

Bên cạnh đó thêm vào persistence.xml:

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

Hãy kể rằng Jdbc4Array là không có mặt trong trình điều khiển Postgre JDBC nữa, xin vui lòng thay vì sử dụng:

org.postgresql.jdbc.PgArray 

Xem ở đây: Package org.postgresql.jdbc4 is missing since 9.4-1207

7

Trong this article, Tôi giải thích làm thế nào bạn có thể đến với một loại Mảng chung mà bạn có thể chỉ thích ứng với các loại cụ thể khác nhau như String[] hoặc int[].

Bạn không cần phải tạo ra tất cả các loại bằng tay, bạn chỉ có thể có được chúng thông qua Maven Trung ương sử dụng phụ thuộc sau:

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

Mọi chi tiết, kiểm tra hibernate-types open-source project.

Giả sử bạn có bảng này trong cơ sở dữ liệu của bạn:

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

Và bạn muốn ánh xạ nó như thế này:

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

Bạn cần phải xác định StringArrayType như thế này:

public class StringArrayType 
     extends AbstractSingleColumnStandardBasicType<String[]> 
     implements DynamicParameterizedType { 

    public StringArrayType() { 
     super( 
      ArraySqlTypeDescriptor.INSTANCE, 
      StringArrayTypeDescriptor.INSTANCE 
     ); 
    } 

    public String getName() { 
     return "string-array"; 
    } 

    @Override 
    protected boolean registerUnderJavaType() { 
     return true; 
    } 

    @Override 
    public void setParameterValues(Properties parameters) { 
     ((StringArrayTypeDescriptor) 
      getJavaTypeDescriptor()) 
      .setParameterValues(parameters); 
    } 
} 

Bạn cần xác định IntArrayType như thế này:

public class IntArrayType 
     extends AbstractSingleColumnStandardBasicType<int[]> 
     implements DynamicParameterizedType { 

    public IntArrayType() { 
     super( 
      ArraySqlTypeDescriptor.INSTANCE, 
      IntArrayTypeDescriptor.INSTANCE 
     ); 
    } 

    public String getName() { 
     return "int-array"; 
    } 

    @Override 
    protected boolean registerUnderJavaType() { 
     return true; 
    } 

    @Override 
    public void setParameterValues(Properties parameters) { 
     ((IntArrayTypeDescriptor) 
      getJavaTypeDescriptor()) 
      .setParameterValues(parameters); 
    } 
} 

Cả String và loại Int chia sẻ ArraySqlTypeDescriptor:

public class ArraySqlTypeDescriptor 
    implements SqlTypeDescriptor { 

    public static final ArraySqlTypeDescriptor INSTANCE = 
     new ArraySqlTypeDescriptor(); 

    @Override 
    public int getSqlType() { 
     return Types.ARRAY; 
    } 

    @Override 
    public boolean canBeRemapped() { 
     return true; 
    } 

    @Override 
    public <X> ValueBinder<X> getBinder(
     JavaTypeDescriptor<X> javaTypeDescriptor) { 
     return new BasicBinder<X>(javaTypeDescriptor, this) { 
      @Override 
      protected void doBind(
        PreparedStatement st, 
        X value, 
        int index, 
        WrapperOptions options 
       ) throws SQLException { 

       AbstractArrayTypeDescriptor<Object> abstractArrayTypeDescriptor = 
        (AbstractArrayTypeDescriptor<Object>) 
         javaTypeDescriptor; 

       st.setArray( 
        index, 
        st.getConnection().createArrayOf(
         abstractArrayTypeDescriptor.getSqlArrayType(), 
         abstractArrayTypeDescriptor.unwrap( 
          value, 
          Object[].class, 
          options 
         ) 
        ) 
       ); 
      } 

      @Override 
      protected void doBind(
        CallableStatement st, 
        X value, 
        String name, 
        WrapperOptions options 
       ) throws SQLException { 
       throw new UnsupportedOperationException( 
        "Binding by name is not supported!" 
       ); 
      } 
     }; 
    } 

    @Override 
    public <X> ValueExtractor<X> getExtractor(
     final JavaTypeDescriptor<X> javaTypeDescriptor) { 
     return new BasicExtractor<X>(javaTypeDescriptor, this) { 
      @Override 
      protected X doExtract(
        ResultSet rs, 
        String name, 
        WrapperOptions options 
       ) throws SQLException { 
       return javaTypeDescriptor.wrap(
        rs.getArray(name), 
        options 
       ); 
      } 

      @Override 
      protected X doExtract(
        CallableStatement statement, 
        int index, 
        WrapperOptions options 
       ) throws SQLException { 
       return javaTypeDescriptor.wrap(
        statement.getArray(index), 
        options 
       ); 
      } 

      @Override 
      protected X doExtract(
        CallableStatement statement, 
        String name, 
        WrapperOptions options 
       ) throws SQLException { 
       return javaTypeDescriptor.wrap(
        statement.getArray(name), 
        options 
       ); 
      } 
     }; 
    } 
} 

Bạn cũng cần phải xác định mô tả Java.

public class StringArrayTypeDescriptor 
     extends AbstractArrayTypeDescriptor<String[]> { 

    public static final StringArrayTypeDescriptor INSTANCE = 
     new StringArrayTypeDescriptor(); 

    public StringArrayTypeDescriptor() { 
     super(String[].class); 
    } 

    @Override 
    protected String getSqlArrayType() { 
     return "text"; 
    } 
} 

public class IntArrayTypeDescriptor 
     extends AbstractArrayTypeDescriptor<int[]> { 

    public static final IntArrayTypeDescriptor INSTANCE = 
     new IntArrayTypeDescriptor(); 

    public IntArrayTypeDescriptor() { 
     super(int[].class); 
    } 

    @Override 
    protected String getSqlArrayType() { 
     return "integer"; 
    } 
} 

Phần lớn việc xử lý loại Java-to-JDBC được bao gồm trong lớp AbstractArrayTypeDescriptor cơ sở:

public abstract class AbstractArrayTypeDescriptor<T> 
     extends AbstractTypeDescriptor<T> 
     implements DynamicParameterizedType { 

    private Class<T> arrayObjectClass; 

    @Override 
    public void setParameterValues(Properties parameters) { 
     arrayObjectClass = ((ParameterType) parameters 
      .get(PARAMETER_TYPE)) 
      .getReturnedClass(); 

    } 

    public AbstractArrayTypeDescriptor(Class<T> arrayObjectClass) { 
     super( 
      arrayObjectClass, 
      (MutabilityPlan<T>) new MutableMutabilityPlan<Object>() { 
       @Override 
       protected T deepCopyNotNull(Object value) { 
        return ArrayUtil.deepCopy(value); 
       } 
      } 
     ); 
     this.arrayObjectClass = arrayObjectClass; 
    } 

    @Override 
    public boolean areEqual(Object one, Object another) { 
     if (one == another) { 
      return true; 
     } 
     if (one == null || another == null) { 
      return false; 
     } 
     return ArrayUtil.isEquals(one, another); 
    } 

    @Override 
    public String toString(Object value) { 
     return Arrays.deepToString((Object[]) value); 
    } 

    @Override 
    public T fromString(String string) { 
     return ArrayUtil.fromString(
      string, 
      arrayObjectClass 
     ); 
    } 

    @SuppressWarnings({ "unchecked" }) 
    @Override 
    public <X> X unwrap(
      T value, 
      Class<X> type, 
      WrapperOptions options 
     ) { 
     return (X) ArrayUtil.wrapArray(value); 
    } 

    @Override 
    public <X> T wrap(
      X value, 
      WrapperOptions options 
     ) { 
     if(value instanceof Array) { 
      Array array = (Array) value; 
      try { 
       return ArrayUtil.unwrapArray( 
        (Object[]) array.getArray(), 
        arrayObjectClass 
       ); 
      } 
      catch (SQLException e) { 
       throw new IllegalArgumentException(e); 
      } 
     } 
     return (T) value; 
    } 

    protected abstract String getSqlArrayType(); 
} 

AbstractArrayTypeDescriptor dựa vào ArrayUtil để xử lý các mảng Java sao chép sâu, đóng gói và unwrapping logic.

Bây giờ, khi bạn chèn một vài thực thể;

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 sẽ tạo ra các câu lệnh SQL sau:

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 
) 
Các vấn đề liên quan