Mappatura di un array PostgreSQL con Hibernate

qualcuno ha mappato con successo un array numerico in PostgreSQL su un array numerico in java tramite 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}'); 

Mappatura:

       

class:

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

ottengo un’eccezione durante l’interrogazione della tabella.

Hibernate non supporta gli array di database (ad esempio quelli mappati su java.sql.Array ) fuori dalla scatola.

array tipi array e primitive-array forniti da Hibernate servono per mappare gli array Java nella backing table – sono fondamentalmente una variante dei mapping one-to-many / collection-of-elements, quindi non è quello che vuoi.

L’ultimo driver JDBC PostgreSQL (8.4.whatever) supporta il metodo Connection.createArrayOf() JDBC4 e anche i metodi ResultSet.getArray() e PreparedStatement.setArray () , quindi è ansible scrivere il proprio UserType per fornire il supporto dell’array.

Ecco un’implementazione UserType che si occupa dell’array Oracle che fornisce un buon punto di partenza, è ragionevolmente semplice adattarlo per gestire invece java.sql.Array .

In questo articolo , ho spiegato come si può ottenere un tipo di array generico che si può semplicemente adattare a vari tipi specifici come String[] o int[] .

Non devi creare manualmente tutti questi tipi, puoi semplicemente ottenerli tramite Maven Central usando la seguente dipendenza:

  com.vladmihalcea hibernate-types-52 ${hibernate-types.version}  

Per maggiori informazioni, consulta il progetto open-type tipo hibernate .

Supponendo che tu abbia questa tabella nel tuo database:

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

E vuoi mapparlo in questo modo:

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

È necessario definire StringArrayType questo modo:

 public class StringArrayType extends AbstractSingleColumnStandardBasicType 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); } } 

È necessario definire IntArrayType questo modo:

 public class IntArrayType extends AbstractSingleColumnStandardBasicType 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); } } 

Entrambi i tipi String e Int condividono 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  ValueBinder getBinder( JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this) { @Override protected void doBind( PreparedStatement st, X value, int index, WrapperOptions options ) throws SQLException { AbstractArrayTypeDescriptor abstractArrayTypeDescriptor = (AbstractArrayTypeDescriptor) 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  ValueExtractor getExtractor( final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor(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 ); } }; } } 

È inoltre necessario definire i descrittori Java.

 public class StringArrayTypeDescriptor extends AbstractArrayTypeDescriptor { public static final StringArrayTypeDescriptor INSTANCE = new StringArrayTypeDescriptor(); public StringArrayTypeDescriptor() { super( String[].class ); } @Override protected String getSqlArrayType() { return "text"; } } public class IntArrayTypeDescriptor extends AbstractArrayTypeDescriptor { public static final IntArrayTypeDescriptor INSTANCE = new IntArrayTypeDescriptor(); public IntArrayTypeDescriptor() { super( int[].class ); } @Override protected String getSqlArrayType() { return "integer"; } } 

La maggior parte della gestione dei tipi Java-to-JDBC è inclusa nella class base AbstractArrayTypeDescriptor :

 public abstract class AbstractArrayTypeDescriptor extends AbstractTypeDescriptor implements DynamicParameterizedType { private Class arrayObjectClass; @Override public void setParameterValues(Properties parameters) { arrayObjectClass = ( (ParameterType) parameters .get( PARAMETER_TYPE ) ) .getReturnedClass(); } public AbstractArrayTypeDescriptor(Class arrayObjectClass) { super( arrayObjectClass, (MutabilityPlan) new MutableMutabilityPlan() { @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 unwrap( T value, Class type, WrapperOptions options ) { return (X) ArrayUtil.wrapArray( value ); } @Override public  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 si basa su ArrayUtil per gestire la logica di copia, wrapping e unwrapping di array Java.

Ora, quando inserisci un paio di quadro;

 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 genererà le seguenti istruzioni 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 ) 

Forse questo è utile per qualcun altro: ho scoperto che nel mio caso si comporta male e non può essere usato con c3p0. (Ho solo esplorato brevemente questi problemi, è ansible risolverli per favore correggimi!)

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 returnedClass() { return int[].class; } @Override public int[] sqlTypes() { return new int[] { SQLTYPE }; } } 

Questo è stato testato contro gli array di stringhe. Forse alcune modifiche nel convertitore sono richieste per gli array numerici. Funziona con Spring JPA.

1) aggiungi PostgreSQLTextArray al tuo progetto

 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 text 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> 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> 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> 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> map) throws SQLException { throw new UnsupportedOperationException(); } @Override public void free() throws SQLException { } } 

2) Aggiungi ListToArrayConverter al tuo codice

 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, Object> { @Override public PostgreSQLTextArray convertToDatabaseColumn(List attribute) { if (attribute == null || attribute.isEmpty()) { return null; } String[] rst = new String[attribute.size()]; return new PostgreSQLTextArray(attribute.toArray(rst)); } @Override public List convertToEntityAttribute(Object dbData) { List 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) Usalo!

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

Sono stato in grado di salvare String[] in PostgreSQL 9.4 e EclipseLink 2.6.2 tramite l’approccio JPA Converter postato qui

che sembra essere la fonte della risposta di

Tk421 del 1 ° luglio 2016.

Anche il caricamento di una matrice da DB funziona correttamente.

Inoltre aggiunto a persistence.xml :

  com.ssg.fcp.fcp_e761.therealthing.backend.jpa.convert.ListToArrayConverter  

Si prega di ricordare che Jdbc4Array non è più presente nel driver JDBC di Postgre, si prega invece di utilizzare:

 org.postgresql.jdbc.PgArray 

Vedi qui: Il pacchetto org.postgresql.jdbc4 manca dal 9.4-1.207

Ecco l’ int[] UserType che ho usato per fare ciò che stai nullSafeGet() che include anche i controlli null per nullSafeGet() e 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 returnedClass() { return int[].class; } @Override public int[] sqlTypes() { return new int[] { SQLTYPE }; } }