2009-10-30 15 views
28

hat jemand erfolgreich ein numerisches Array in PostgreSQL zu einem numerischen Array in Java über Hibernate?Mapping eines PostgreSQL-Arrays mit 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}'); 

Mapping:

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

Klasse:

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

ich eine Ausnahme erhalten, wenn die Tabelle abfragt.

Antwort

22

Hibernate unterstützt keine Datenbank-Arrays (z. B. solche, die auf java.sql.Array gemappt sind).

array und primitive-array Typen, die von Hibernate zur Verfügung gestellt werden, sind zum Mappen von Java-Arrays in Backing-Tabelle - sie sind im Grunde eine Variation von Eins-zu-Viele/Sammlung von Elementen Mappings, so dass Sie nicht wollen.

Neueste PostgreSQL JDBC-Treiber (8.4.whatever) unterstützt JDBC4 Connection.createArrayOf() Verfahren sowie ResultSet.getArray() und PreparedStatement.setArray() Methoden, obwohl, so können Sie Ihre eigenen UserType schreiben Array Unterstützung.

Here ist eine UserType-Implementierung im Umgang mit Oracle-Array, die einen guten Ausgangspunkt bietet, es ist relativ einfach, es anzupassen, um stattdessen java.sql.Array zu handhaben.

+12

Falls jemand frage mich, was das letzte Glied ist über (es ist jetzt eine 404), die Wayback Machine Link hier - http : //web.archive.org/web/20090325101739/http: //www.hibernate.org/393.html – Sam

7

Vielleicht ist dies für jemand anderen nützlich: Ich fand, dass es in meinem Fall schlecht funktioniert und nicht mit c3p0 verwendet werden konnte. (Es werden nur diese Fragen kurz erkundet, wird sie mir bitte korrigieren gelöst werden!)

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

Die 'nullSafeGet' und' nullSafeSet' funktionieren nicht, wenn 'int []' 'null' ist. Sie müssen nach Nullwerten suchen. –

3

Diese gegen String-Arrays getestet wurde. Vielleicht sind einige Änderungen im Konverter für numerische Arrays erforderlich. Dies funktioniert mit Spring JPA.

1) hinzufügen PostgreSQLTextArray zu einem Projekt

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) hinzufügen ListToArrayConverter, um Ihren Code

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) Verwenden Sie es! Hier

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

    [...] 

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

    [...] 
1

ist die int[] Usertype Früher habe ich zu tun, was Sie nach dem auch die Null-Kontrollen für nullSafeGet() und nullSafeSet() umfasst:

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

konnte ich ein String[] zu PostgreSQL 9.4 speichern und EclipseLink 2.6.2 über den PPV Converter Ansatz geschrieben here

, die die Quelle für die Antwort von

TK421 1. Juli 2016.

Laden eines Array von DB auch gut funktioniert zu sein scheint.

Zusätzlich zu persistence.xml hinzugefügt:

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

Bitte erwähnen, dass Jdbc4Array in Postgre JDBC-Treiber nicht vorhanden ist mehr, bitte verwenden Sie stattdessen:

org.postgresql.jdbc.PgArray 

Siehe hier: Package org.postgresql.jdbc4 is missing since 9.4-1207

7

In this article, Ich habe erklärt, wie Sie einen generischen Array-Typ entwickeln können, den Sie einfach an verschiedene spezifische Typen wie String[] oder int[] anpassen können.

Sie müssen all diese Typen nicht manuell erstellen, geben Sie einfach sie über Maven Zentrale mit der folgenden Abhängigkeit erhalten können:

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

Für weitere Informationen, besuchen Sie die hibernate-types open-source project.

Vorausgesetzt, dass Sie diese Tabelle in Ihrer Datenbank haben:

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

Und wollen Sie es wie folgt zuzuordnen:

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

Sie müssen die StringArrayType wie folgt definieren:

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

Sie müssen diedefinierenwie folgt aus:

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

Sowohl die Streich- und Int-Typen teilen die 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 
       ); 
      } 
     }; 
    } 
} 

Sie müssen auch die Java-Deskriptoren definieren.

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"; 
    } 
} 

Der größte Teil der Java-to-JDBC Typ Handhabung ist in der AbstractArrayTypeDescriptor Basisklasse enthalten:

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 auf ArrayUtil setzt die Java-Array tief Kopieren, Wickeln und Abwickeln Logik zu behandeln.

Jetzt, wenn Sie ein paar Entitäten einfügen;

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 die folgenden SQL-Anweisungen wird generieren:

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 
)