2009-10-30 14 views
28

¿alguien ha mapeado con éxito una matriz numérica en PostgreSQL a una matriz numérica en java a través de Hibernate?Mapeo de una matriz PostgreSQL con 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}'); 

mapeo:

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

clase:

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

me sale una excepción cuando se consulta la tabla.

Respuesta

22

Hibernate no es compatible con matrices de bases de datos (por ejemplo, las asignadas a java.sql.Array) fuera de la caja.

array y primitive-array tipos proporcionados por Hibernate son para las matrices de Java asignación en la tabla de respaldo - son básicamente una variación de/asignaciones de recopilación de elementos de uno a muchos, de manera que no es lo que quieres.

controlador más último PostgreSQL JDBC (8.4.whatever) soporta JDBC4 Connection.createArrayOf() método, así como ResultSet.getArray() y PreparedStatement.setArray() métodos, sin embargo, por lo que puede escribir su propio UserType para proporcionar soporte matriz.

Here es una implementación de UserType que trata con la matriz de Oracle que proporciona un buen punto de partida, es bastante sencillo adaptarla para manejar java.sql.Array.

+12

En caso de que alguien más estaba preguntando lo que el último enlace es aproximadamente (que es ahora un 404), el enlace Wayback Machine es aquí - http : //web.archive.org/web/20090325101739/http: //www.hibernate.org/393.html – Sam

7

Quizás esto es útil para otra persona: encontré que en mi caso funciona mal y no se podía usar con c3p0. (Sólo exploró estos temas brevemente, es que pueden ser resueltos por favor me corrija!)

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

Los 'nullSafeGet' y' nullSafeSet' no funcionan si 'int []' es 'null'. Necesita verificar valores nulos. –

3

Esto ha sido probado en contra de las matrices de cadenas. Tal vez se necesiten algunas modificaciones en el convertidor para las matrices numéricas. Esto funciona con Spring JPA.

1) agregar a su proyecto 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) Añadir ListToArrayConverter a su código

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) Se usa!

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

    [...] 

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

    [...] 
1

Aquí es el UserType int[] que solía hacer lo que está buscando, que también incluye los cheques nulos para nullSafeGet() y 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

yo era capaz de guardar un String[] a PostgreSQL 9.4 y EclipseLink 2.6.2 a través del enfoque de la APP convertidor publicado here

que parece ser la fuente de la respuesta de

TK421 del 1 de julio de 2016.

Cargando una matriz de DB también funciona bien.

añade Adicionalmente a persistence.xml:

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

favor mencione que Jdbc4Array no está presente en el driver de PostgreSQL JDBC más, por favor, en vez de usar:

org.postgresql.jdbc.PgArray 

Ver aquí: Package org.postgresql.jdbc4 is missing since 9.4-1207

7

En this article, Le expliqué cómo puede crear un tipo de matriz genérico que simplemente puede adaptar a varios tipos específicos, como String[] o int[].

Usted no tiene que crear todos estos tipos de forma manual, se puede conseguir simplemente ellos a través de Maven central utilizando la siguiente dependencia:

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

Para obtener más información, echa un vistazo a la hibernate-types open-source project.

Asumiendo que tiene esta tabla en su base de datos:

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

Y que desea asignar esta manera:

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

Es necesario definir el StringArrayType así:

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

Necesita definir el IntArrayType así:

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

Tanto la cadena y los tipos Int comparten la 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 
       ); 
      } 
     }; 
    } 
} 

También es necesario definir los descriptores de 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"; 
    } 
} 

La mayor parte de la manipulación de tipo Java-a-JDBC se incluye en la clase de base AbstractArrayTypeDescriptor:

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 se basa en ArrayUtil para manejar la matriz de Java copia completa y embalaje, de la lógica desenvolver.

Ahora, cuando inserta un par de entidades;

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 va a generar las siguientes sentencias 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 
) 
Cuestiones relacionadas