anotación adecuada de hibernación para byte

Tengo una aplicación que utiliza hibernate 3.1 y anotaciones JPA. Tiene algunos objetos con atributos byte [] (1k – 200k de tamaño). Utiliza la anotación JPA @Lob e hibernate 3.1 puede leerlos perfectamente en todas las bases de datos principales; parece ocultar las peculiaridades del proveedor de Blob JDBC (como debería ser).

@Entity public class ConfigAttribute { @Lob public byte[] getValueBuffer() { return m_valueBuffer; } } 

Tuvimos que actualizar a 3.5, cuando descubrimos que hibernate 3.5 rompe (y no va a arreglar) esta combinación de anotación en postgresql (sin solución alternativa). Hasta ahora no he encontrado una solución clara, pero me di cuenta de que si elimino el @Lob, usará el tipo de postgresql bytea (que funciona, pero solo en postgres).

 annotation postgres oracle works on ------------------------------------------------------------- byte[] + @Lob oid blob oracle byte[] bytea raw(255) postgresql byte[] + @Type(PBA) oid blob oracle byte[] + @Type(BT) bytea blob postgresql once you use @Type, @Lob seems to not be relevant note: oracle seems to have deprecated the "raw" type since 8i. 

Estoy buscando una forma de tener una sola clase anotada (con una propiedad blob) que sea portable en las principales bases de datos.

  • ¿Cuál es la forma portátil de anotar una propiedad byte []?
  • ¿Esto está arreglado en alguna versión reciente de Hibernate?

Actualización: Después de leer este blog , finalmente descubrí cuál era la solución original en el problema de JIRA: Aparentemente se supone que debes soltar @Lob y anotar la propiedad como:

 @Type(type="org.hibernate.type.PrimitiveByteArrayBlobType") byte[] getValueBuffer() {... 

Sin embargo, esto no funciona para mí , sigo recibiendo OID en lugar de bytea; sin embargo, funcionó para el autor del problema JIRA, que parecía querer oid.

Después de la respuesta de A. García, probé este combo, que realmente funciona en postgresql, pero no en oracle.

 @Type(type="org.hibernate.type.BinaryType") byte[] getValueBuffer() {... 

Lo que realmente necesito hacer es controlar qué @org.hibernate.annotations.Type la combinación (@Lob + byte [] se asigna) a (en postgresql).


Aquí está el fragmento de 3.5.5.Final de MaterializedBlobType (sql tipo Blob). Según el blog de Steve, postgresql quiere que uses Streams para bytea (no me preguntes por qué) y el tipo de Blob personalizado de postgresql para oids. Tenga en cuenta también que usar setBytes () en JDBC también es para bytea (de la experiencia pasada). Entonces esto explica por qué las secuencias de uso no tienen ningún efecto, ambas asumen ‘bytea’.

 public void set(PreparedStatement st, Object value, int index) { byte[] internalValue = toInternalFormat( value ); if ( Environment.useStreamsForBinary() ) { // use streams = true st.setBinaryStream( index, new ByteArrayInputStream( internalValue ), internalValue.length ); } else { // use streams = false st.setBytes( index, internalValue ); } } 

Esto resulta en:

 ERROR: column "signature" is of type oid but expression is of type bytea 

Actualización La siguiente pregunta lógica es: “¿por qué no simplemente cambiar las definiciones de tabla manualmente a bytea” y mantener el (@Lob + byte [])? Esto funciona, HASTA que intente almacenar un byte nulo []. Lo que el controlador postgreSQL piensa que es una expresión de tipo OID y el tipo de columna es bytea; esto se debe a que hibernate (correctamente) llama a JDBC.setNull () en lugar de a JDBC.setBytes (nulo) que espera el controlador PG.

 ERROR: column "signature" is of type bytea but expression is of type oid 

El sistema de tipo en hibernación es actualmente un “trabajo en progreso” (de acuerdo con el comentario de desaprobación 3.5.5). De hecho, gran parte del código 3.5.5 está en desuso, es difícil saber qué mirar al subclasificar el PostgreSQLDialect).

AFAKT, Types.BLOB / ‘oid’ en postgresql se debe asignar a algún tipo personalizado que use el acceso JDBC de estilo OID (es decir, el objeto PostgresqlBlobType y NOT MaterializedBlobType). Nunca he usado Blobs con postgresql, pero sé que bytea simplemente funciona como uno / lo esperaría.

Actualmente estoy mirando BatchUpdateException: es posible que el controlador no admita el procesamiento por lotes.


Gran cita de 2004: “Para resumir mis divagaciones, diría que deberíamos esperar a que el controlador JDBC haga las LOB correctamente antes de cambiar a Hibernate”.

Referencias

  • https://forum.hibernate.org/viewtopic.php?p=2393203
  • https://forum.hibernate.org/viewtopic.php?p=2435174
  • http://hibernate.atlassian.net/browse/HHH-4617
  • http://postgresql.1045698.n5.nabble.com/Migration-to-Hibernate-3-5-final-td2175339.html
  • https://jira.springframework.org/browse/SPR-2318
  • https://forums.hibernate.org/viewtopic.php?p=2203382&sid=b526a17d9cf60a80f13d40cf8082aafd
  • http://virgo47.wordpress.com/2008/06/13/jpa-postgresql-and-bytea-vs-oid-type/

¿Cuál es la forma portátil de anotar una propiedad byte []?

Depende de lo que quieras. JPA puede persistir un byte[] no anotado byte[] . De la especificación JPA 2.0:

11.1.6 Anotación básica

La anotación Basic es el tipo más simple de asignación a una columna de base de datos. La anotación Basic se puede aplicar a una propiedad persistente o variable de instancia de cualquiera de los siguientes tipos: primitiva Java, tipos, envoltorios de los tipos primitivos, java.lang.String , java.math.BigInteger , java.math.BigDecimal , java.util.Date , java.util.Calendar , java.sql.Date , java.sql.Time , java.sql.Timestamp , byte[] , Byte[] , char[] , Character[] , enumeraciones y cualquier otro tipo que implementa Serializable . Como se describe en la Sección 2.8, el uso de la anotación Basic es opcional para campos persistentes y propiedades de estos tipos. Si la anotación Básica no está especificada para dicho campo o propiedad, se aplicarán los valores predeterminados de la anotación Básica.

¿Y Hibernate asignará un “por defecto” a un VARBINARY SQL (o un LONGVARBINARY SQL dependiendo del tamaño de Column ?) Que PostgreSQL maneja con un bytea .

Pero si desea que el byte[] se almacene en un objeto grande, debe usar un @Lob . De la especificación:

11.1.24 Anotación Lob

Una anotación de Lob especifica que una propiedad o campo persistente debe persistir como un objeto grande para un tipo de objeto grande admitido por la base de datos. Las aplicaciones portátiles deben usar la anotación Lob al mapear a un tipo de Lob base de datos. La anotación Lob se puede usar junto con la anotación Básica o con la anotación ElementCollection cuando el valor de la colección de elementos es de tipo básico. Un Lob puede ser de tipo binario o de carácter. El tipo de Lob se infiere del tipo de campo o propiedad persistente y, a excepción de los tipos de cadena y caracteres, se establece de manera predeterminada en Blob.

Y Hibernate lo asignará a un BLOB SQL que PostgreSQL maneja con un oid .

¿Esto está arreglado en alguna versión reciente de Hibernate?

Bueno, el problema es que no sé cuál es el problema exactamente. Pero al menos puedo decir que nada ha cambiado desde 3.5.0-Beta-2 (que es donde se ha introducido un cambio) en la twig 3.5.x.

Pero mi comprensión de problemas como HHH-4876 , HHH-4617 y PostgreSQL y BLOB (mencionados en el javadoc de PostgreSQLDialect ) es que se supone que debe establecer la siguiente propiedad

 hibernate.jdbc.use_streams_for_binary=false 

si quieres usar oid ie byte[] con @Lob (que es mi entendimiento ya que VARBINARY no es lo que quieres con Oracle). ¿Has intentado esto?

Como alternativa, HHH-4876 sugiere usar el PrimitiveByteArrayBlobType desuso para obtener el comportamiento anterior (pre Hibernate 3.5).

Referencias

  • Especificación JPA 2.0
    • Sección 2.8 “Asignación de valores predeterminados para campos o propiedades sin relación”
    • Sección 11.1.6 “Anotación básica”
    • Sección 11.1.24 “Anotación Lob”

Recursos

Aquí va lo que dice O’reilly Enterprise JavaBeans, 3.0

JDBC tiene tipos especiales para estos objetos muy grandes. El tipo java.sql.Blob representa datos binarios , y java.sql.Clob representa datos de caracteres.

Aquí va el código fuente de PostgreSQLDialect

 public PostgreSQLDialect() { super(); ... registerColumnType(Types.VARBINARY, "bytea"); /** * Notice it maps java.sql.Types.BLOB as oid */ registerColumnType(Types.BLOB, "oid"); } 

Entonces, ¿qué puedes hacer?

Anule PostgreSQLDialect de la siguiente manera

 public class CustomPostgreSQLDialect extends PostgreSQLDialect { public CustomPostgreSQLDialect() { super(); registerColumnType(Types.BLOB, "bytea"); } } 

Ahora solo defina su dialecto personalizado

  

Y usa tu anotación portátil JPA @Lob

 @Lob public byte[] getValueBuffer() { 

ACTUALIZAR

Aquí se ha extraído aquí

Tengo una aplicación ejecutándose en hibernate 3.3.2 y las aplicaciones funcionan bien , con todos los campos blob usando oid (byte [] en java)

Migrar a hibernate 3.5 todos los campos de blob ya no funcionan , y el registro del servidor muestra: ERROR org.hibernate.util.JDBCExceptionReporter – ERROR: column es de tipo oid pero la expresión es de tipo bytea

que se puede explicar aquí

Esto en general no es un error en PG JDBC , pero el cambio de la implementación predeterminada de Hibernate en la versión 3.5 . En mi situación , la propiedad compatible de conexión no ayudó .

Mucho más esto es lo que vi en 3.5 – beta 2, y no sé si esto fue arreglado es Hibernate – sin la anotación @Type – creará automáticamente la columna de tipo oid, pero intentará leer esto como bytea

Es interesante porque cuando asigna tipos.BOLB como bytea (ver CustomPostgreSQLDialect) obtiene

No se pudo ejecutar la actualización por lotes JDBC

al insertar o actualizar

Finalmente tengo esto funcionando. Se expande en la solución de A. Garcia, sin embargo, dado que el problema radica en el tipo de hibernación tipo MaterializedBlob, el solo mapeo de Blob> bytea no es suficiente, necesitamos un reemplazo para MaterializedBlobType que funciona con hibernadamente el soporte blob roto. Esta implementación solo funciona con bytea, pero tal vez el tipo del problema de JIRA que quería OID podría contribuir con la implementación de OID.

Tristemente reemplazar estos tipos en el tiempo de ejecución es un dolor, ya que deberían ser parte del Dialecto. Si solo esta mejora de JIRA llega a 3.6, sería posible.

 public class PostgresqlMateralizedBlobType extends AbstractSingleColumnStandardBasicType { public static final PostgresqlMateralizedBlobType INSTANCE = new PostgresqlMateralizedBlobType(); public PostgresqlMateralizedBlobType() { super( PostgresqlBlobTypeDescriptor.INSTANCE, PrimitiveByteArrayTypeDescriptor.INSTANCE ); } public String getName() { return "materialized_blob"; } } 

Mucho de esto podría ser probablemente estático (¿realmente necesita getBinder () una nueva instancia?), Pero realmente no entiendo el hibernate interno así que esto es principalmente copy + paste + modify.

 public class PostgresqlBlobTypeDescriptor extends BlobTypeDescriptor implements SqlTypeDescriptor { public static final BlobTypeDescriptor INSTANCE = new PostgresqlBlobTypeDescriptor(); public  ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new PostgresqlBlobBinder(javaTypeDescriptor, this); } public  ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor( javaTypeDescriptor, this ) { protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { return (X)rs.getBytes(name); } }; } } public class PostgresqlBlobBinder implements ValueBinder { private final JavaTypeDescriptor javaDescriptor; private final SqlTypeDescriptor sqlDescriptor; public PostgresqlBlobBinder(JavaTypeDescriptor javaDescriptor, SqlTypeDescriptor sqlDescriptor) { this.javaDescriptor = javaDescriptor; this.sqlDescriptor = sqlDescriptor; } ... public final void bind(PreparedStatement st, J value, int index, WrapperOptions options) throws SQLException { st.setBytes(index, (byte[])value); } } 

Estoy usando el Hibernate 4.2.7.SP1 con Postgres 9.3 y los siguientes trabajos para mí:

 @Entity public class ConfigAttribute { @Lob public byte[] getValueBuffer() { return m_valueBuffer; } } 

como Oracle no tiene problemas con eso, y para Postgres estoy usando un dialecto personalizado:

 public class PostgreSQLDialectCustom extends PostgreSQL82Dialect { @Override public SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { if (sqlTypeDescriptor.getSqlType() == java.sql.Types.BLOB) { return BinaryTypeDescriptor.INSTANCE; } return super.remapSqlTypeDescriptor(sqlTypeDescriptor); } } 

la ventaja de esta solución, considero, es que puedo mantener intactos los flasks de hibernación.

Para ver más problemas de compatibilidad de Postgres / Oracle con Hibernate, consulte la publicación de mi blog .

Solucioné Mi problema agregando la anotación de @Lob que creará el byte [] en el oracle como blob, pero esta anotación creará el campo como un oid que no funciona correctamente. Para hacer un byte [] creado como bytea. postgres como abajo

 Public class PostgreSQLDialectCustom extends PostgreSQL82Dialect { public PostgreSQLDialectCustom() { System.out.println("Init PostgreSQLDialectCustom"); registerColumnType( Types.BLOB, "bytea" ); } @Override public SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { if (sqlTypeDescriptor.getSqlType() == java.sql.Types.BLOB) { return BinaryTypeDescriptor.INSTANCE; } return super.remapSqlTypeDescriptor(sqlTypeDescriptor); } } 

También es necesario anular el parámetro para el dialecto

spring.jpa.properties.hibernate.dialect = com.ntg.common.DBCompatibilityHelper.PostgreSQLDialectCustom

más sugerencias se pueden encontrar en ella: https://dzone.com/articles/postgres-and-oracle

Lo hice funcionar anulando anotación con archivo XML para Postgres. La anotación se mantiene para Oracle. En mi opinión, en este caso sería mejor que anulemos el mapeo de este problema, alguna entidad con mapeo xml. Podemos anular entidades individuales / múltiples con mapeo xml. Así que usaríamos la anotación para nuestra base de datos principalmente soportada, y un archivo xml para cada otra base de datos.

Nota: solo tenemos que anular una sola clase, por lo que no es un gran problema. Lea más de mi ejemplo Ejemplo para anular la anotación con XML

En Postgres @Lob se está rompiendo por byte [] mientras intenta guardarlo como oid, y para String también ocurre el mismo problema. A continuación, el código se está rompiendo en postgres, que funciona bien en Oracle.

 @Lob private String stringField; 

y

 @Lob private byte[] someByteStream; 

Para corregirlo en postgres, escribe debajo de hibernate.dialect personalizado

 public class PostgreSQLDialectCustom extends PostgreSQL82Dialect{ public PostgreSQLDialectCustom() { super(); registerColumnType(Types.BLOB, "bytea"); } @Override public SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { if (Types.CLOB == sqlTypeDescriptor.getSqlType()) { return LongVarcharTypeDescriptor.INSTANCE; } return super.remapSqlTypeDescriptor(sqlTypeDescriptor); } } 

Ahora configura el dialecto personalizado en Hibernate

 hibernate.dialect=XYZPostgreSQLDialectCustom 

XYZ es el nombre del paquete.

Ahora está funcionando bien. NOTA- Mi versión de Hibernate – 5.2.8.Final Postgres versión- 9.6.3

Gracias Justin, Pascal por guiarme en la dirección correcta. También estaba enfrentando el mismo problema con Hibernate 3.5.3. Su investigación y consejos sobre las clases correctas me ayudaron a identificar el problema y solucionarlo.

Para el beneficio de aquellos que aún están atrapados con Hibernate 3.5 y usan la combinación de oid + byte [] + @LoB, lo siguiente es lo que he hecho para solucionar el problema.

  1. Creé un BlobType personalizado extendiendo MaterializedBlobType y reemplazando el set y los métodos get con el acceso al estilo oid.

     public class CustomBlobType extends MaterializedBlobType { private static final String POSTGRESQL_DIALECT = PostgreSQLDialect.class.getName(); /** * Currently set dialect. */ private String dialect = hibernateConfiguration.getProperty(Environment.DIALECT); /* * (non-Javadoc) * @see org.hibernate.type.AbstractBynaryType#set(java.sql.PreparedStatement, java.lang.Object, int) */ @Override public void set(PreparedStatement st, Object value, int index) throws HibernateException, SQLException { byte[] internalValue = toInternalFormat(value); if (POSTGRESQL_DIALECT.equals(dialect)) { try { //I had access to sessionFactory through a custom sessionFactory wrapper. st.setBlob(index, Hibernate.createBlob(internalValue, sessionFactory.getCurrentSession())); } catch (SystemException e) { throw new HibernateException(e); } } else { st.setBytes(index, internalValue); } } /* * (non-Javadoc) * @see org.hibernate.type.AbstractBynaryType#get(java.sql.ResultSet, java.lang.String) */ @Override public Object get(ResultSet rs, String name) throws HibernateException, SQLException { Blob blob = rs.getBlob(name); if (rs.wasNull()) { return null; } int length = (int) blob.length(); return toExternalFormat(blob.getBytes(1, length)); } } 
    1. Registre CustomBlobType con Hibernate. Lo siguiente es lo que hice para lograr eso.

       hibernateConfiguration= new AnnotationConfiguration(); Mappings mappings = hibernateConfiguration.createMappings(); mappings.addTypeDef("materialized_blob", "xyzBlobType", null);