anotación de hibernación adecuada para el byte []

120

Tengo una aplicación que usa hibernate 3.1 y anotaciones JPA. Tiene algunos objetos con atributos de 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 JDBC Blob (como debería hacer).

@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 arregla) esta combinación de anotaciones en postgresql (sin solución alternativa). No he encontrado una solución clara hasta ahora, pero noté que si simplemente elimino el @Lob, usa el tipo bytea de postgresql (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 de blob) que sea portátil en las principales bases de datos.

  • ¿Cuál es la forma portátil de anotar una propiedad de byte []?
  • ¿Está esto 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í , todavía obtengo OID en lugar de bytea; Sin embargo, funcionó para el autor de la edición de la JIRA, que parecía querer oid.

Después de la respuesta de A. García, probé este combo, que en realidad 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. Escriba la combinación (@Lob + byte [] se asigna) a (en postgresql).


Aquí está el fragmento de 3.5.5.Final de MaterializedBlobType (Blob de tipo SQL). 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 el uso de setBytes () en JDBC también es para bytea (de experiencias pasadas). Entonces, esto explica por qué los flujos de uso no tienen ningún efecto, ambos 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 cambiar las definiciones de la tabla manualmente a bytea" y mantener el (@Lob + byte [])? Esto hace el trabajo, HASTA intenta almacenar un byte nulo []. Lo que el controlador postgreSQL cree 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 JDBC.setBytes (nulo) que espera el controlador PG.

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

El sistema de tipos en hibernación es actualmente un 'trabajo en progreso' (según el comentario de desaprobación 3.5.5). De hecho, gran parte del código 3.5.5 está desaprobado, es difícil saber qué mirar al subclasificar PostgreSQLDialect).

AFAKT, Types.BLOB / 'oid' en postgresql debe asignarse a algún tipo personalizado que utilice acceso JDBC de estilo OID (es decir, objeto PostgresqlBlobType y NO MaterializedBlobType). En realidad, nunca usé Blobs con éxito con postgresql, pero sé que bytea simplemente funciona como uno / esperaría.

Actualmente estoy viendo la 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 realice las LOB correctamente antes de cambiar Hibernate".

Referencias:

Justin
fuente
Esto parece haberse solucionado en 3.6, no estoy seguro de 3.5.6; la clase MaterializedBlobType fue totalmente reescrita de 3.5.5> 3.6. El tipo OID ahora funciona desde que cambiaron la implementación.
Justin
¡Agradable! Me pregunto qué problema de Jira está rastreando esta reescritura, si la hay (tal vez la reescritura sea una consecuencia de un cambio más profundo). Sería bueno respaldar los cambios en 3.5, si es posible. Malas noticias si no es posible.
Pascal Thivent
En realidad, mi prueba me dio un falso positivo la primera vez (¡sabía que debería haber esperado!); Todavía no se ha solucionado, el error acaba de pasar a BlobTypeDescriptor.
Justin
Gracias. @Type (type = "org.hibernate.type.BinaryType") funcionó para mí para una tabla que almacena archivos PDF. Migré una base de datos de Oracle a Postgres usando Oracle-To-PostgreSQL de Intelligent Converters, y se convirtió e insertó automáticamente de BLOB a BYTEA, pero BlobType no funcionó para mí.
jmoran

Respuestas:

68

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

Depende de lo que quieras. JPA puede conservar un archivo byte[]. De la especificación JPA 2.0:

11.1.6 Anotación básica

La Basicanotación es el tipo más simple de asignación a una columna de la base de datos. La Basicanotación se puede aplicar a una propiedad o variable de instancia persistente de cualquiera de los siguientes tipos: Java primitivo, 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 Basicanotación es opcional para los campos persistentes y las propiedades de estos tipos. Si la anotación básica no se especifica para dicho campo o propiedad, se aplicarán los valores predeterminados de la anotación básica.

E Hibernate lo mapeará "por defecto" a un SQL VARBINARY(¿o un SQL LONGVARBINARYdependiendo del Columntamaño?) Que PostgreSQL maneja con un bytea.

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

11.1.24 Anotación lob

Una Lobanotación especifica que una propiedad o campo persistente debe conservarse como un objeto grande en un tipo de objeto grande compatible con la base de datos. Las aplicaciones portátiles deben usar la Lobanotación al mapear a un Lobtipo de base de datos . La Lobanotación se puede utilizar junto con la anotación básica o con la ElementCollectionanotación cuando el valor de la colección de elementos es de tipo básico. A Lobpuede ser de tipo binario o de carácter. El Lobtipo se infiere del tipo de propiedad o campo persistente y, a excepción de los tipos de cadena y caracteres, el valor predeterminado es Blob.

E Hibernate lo asignará a un SQL BLOBque PostgreSQL maneja con un oid .

¿Está esto arreglado en alguna versión reciente de hibernate?

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

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

hibernate.jdbc.use_streams_for_binary=false

si desea usar oidie byte[]con @Lob(lo que tengo entendido, ya VARBINARYque no es lo que desea con Oracle). ¿Intentaste esto?

Como alternativa, HHH-4876 sugiere usar el obsoleto PrimitiveByteArrayBlobTypepara obtener el comportamiento anterior (pre Hibernate 3.5).

Referencias

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

Recursos

Pascal Thivent
fuente
Dios mío, me doy cuenta de que esta pregunta ha cambiado mucho desde que comencé a responder. Leeré todos los cambios más tarde y actualizaré mis respuestas después de digerir los cambios si es necesario.
Pascal Thivent
Es bueno ver la especificación, por lo que hibernate es totalmente correcto para mapear (@Lob + byte []) a un tipo de objeto grande compatible. En Postgresql hay 2 (bytea u oid). Sin embargo, mientras hibernate 3.5 se asigna a oid (por defecto), lee usando JDBC getBytes () que el controlador PGSQL devuelve el oid de 6 bytes en lugar de los datos. Tenga en cuenta también que el autor del blog ha respondido de la manera más útil (en su blog) desde que se planteó la pregunta.
Justin
@Justin Sin embargo, mientras hibernate 3.5 se asigna a oid (por defecto) lee usando JDBC getBytes () qué controlador PGSQL devuelve el oid de 6 bytes en lugar de los datos - ¿esto ocurre cuando se usa hibernate.jdbc.use_streams_for_binary=falsetambién? (voy a comprobar lo que dijo Steve ahora).
Pascal Thivent
Voy a intentar especificarlo en el archivo de propiedades, sin embargo, PostgreSQLDialect tiene useInputStreamToInsertBlob () devolviendo falso, así que supongo que sí, ya que no establezco explícitamente esta propiedad.
Justin
Después de establecer esta propiedad (en verdadero o falso), obtengo una excepción de tiempo de ejecución: ERROR: la columna "firma" es de tipo bytea pero la expresión es de tipo oid ". Debo mencionar que estoy usando hibernate 3.5.5.Final + PG 8.2 conductores.
Justin
10

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 que puedes hacer

Anule PostgreSQLDialect de la siguiente manera

public class CustomPostgreSQLDialect extends PostgreSQLDialect {

    public CustomPostgreSQLDialect() {
        super();

        registerColumnType(Types.BLOB, "bytea");
    }
}

Ahora solo define tu dialecto personalizado

<property name="hibernate.dialect" value="br.com.ar.dialect.CustomPostgreSQLDialect"/>

Y use su anotación JPA @Lob portátil

@Lob
public byte[] getValueBuffer() {

ACTUALIZAR

Aquí se ha extraído aquí

Tengo una aplicación que se ejecuta en hibernate 3.3.2 y las aplicaciones funcionan bien , con todos los campos de blob usando oid (byte [] en java)

...

Al 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: la columna es de tipo oid pero la expresión es de tipo bytea

que se puede explicar aquí

Generalmente, esto no es un error en PG JDBC , sino un cambio en la implementación predeterminada de Hibernate en la versión 3.5 . En mi situación, configurar la propiedad compatible en la conexión no ayudó .

...

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

Es interesante porque cuando asigna Types.BOLB como bytea (Ver CustomPostgreSQLDialect) Obtiene

No se pudo ejecutar la actualización por lotes JDBC

al insertar o actualizar

Arthur Ronald
fuente
Esta solución parece gloriosa, la estoy probando ahora.
Justin
Esto genera el DDL correcto, pero falla en tiempo de ejecución: obtengo una excepción java.sql.BatchUpdateException cuando intento un objeto con una propiedad de blob.
Justin
@Justin Pruebe un escenario similar utilizando Oracle en lugar de PostgreSQL y vea lo que obtiene. BatchUpdateException tiene que ver con errores que ocurren durante una operación de actualización por lotes.
Arthur Ronald
En realidad, lo que realmente quiero no es asignar BLOB a "bytea", sino asignar la combinación de anotaciones (byte [] + @Lob) a Types.VARBINARY!
Justin
7

Estoy usando Hibernate 4.2.7.SP1 con Postgres 9.3 y lo siguiente funciona para mí:

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

ya que 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 que considero es que puedo mantener intactos los frascos de hibernación.

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

Peter Butkovic
fuente
2
Me funcionó usando Hibernate 4.3.6 y Postgresql 9.3 con la extensión de Postgresql9Dialect. ¡Gracias!
Andrés Oviedo
Funciona con Hibernate 5.3.7.Final y Postgres95Dialect. Thx
Bernhard Kern
6

Finalmente he conseguido que esto funcione. Sin embargo, amplía la solución de A. García, ya que el problema radica en el tipo de hibernación MaterializedBlob, solo mapear Blob> bytea no es suficiente, necesitamos un reemplazo para MaterializedBlobType que funciona con soporte de blob roto en hibernación. Esta implementación solo funciona con bytea, pero tal vez el tipo del problema de JIRA que quería OID podría contribuir con una implementación de OID.

Lamentablemente, reemplazar estos tipos en tiempo de ejecución es una molestia, ya que deberían ser parte del dialecto. Si solo esta mejora de JIRA llegara a 3.6, sería posible.

public class PostgresqlMateralizedBlobType extends AbstractSingleColumnStandardBasicType<byte[]> {
 public static final PostgresqlMateralizedBlobType INSTANCE = new PostgresqlMateralizedBlobType();

 public PostgresqlMateralizedBlobType() {
  super( PostgresqlBlobTypeDescriptor.INSTANCE, PrimitiveByteArrayTypeDescriptor.INSTANCE );
 }

  public String getName() {
   return "materialized_blob";
  }
}

Gran parte de esto probablemente podría ser estático (¿getBinder () realmente necesita una nueva instancia?), Pero realmente no entiendo la hibernación interna, por lo que esto es principalmente copiar + pegar + modificar.

public class PostgresqlBlobTypeDescriptor extends BlobTypeDescriptor implements SqlTypeDescriptor {
  public static final BlobTypeDescriptor INSTANCE = new PostgresqlBlobTypeDescriptor();

  public <X> ValueBinder<X> getBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) {
   return new PostgresqlBlobBinder<X>(javaTypeDescriptor, this);
  }
  public <X> ValueExtractor<X> getExtractor(final JavaTypeDescriptor<X> javaTypeDescriptor) {
   return new BasicExtractor<X>( javaTypeDescriptor, this ) {
    protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { 
      return (X)rs.getBytes(name);
    }
   };
  }
}

public class PostgresqlBlobBinder<J> implements ValueBinder<J> {
 private final JavaTypeDescriptor<J> javaDescriptor;
 private final SqlTypeDescriptor sqlDescriptor;

 public PostgresqlBlobBinder(JavaTypeDescriptor<J> 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);
 }
}
Justin
fuente
+1 por su investigación. Felicidades. Solo un consejo: prefiere editar su propia pregunta / respuesta hasta 8 veces. De lo contrario, su pregunta / respuesta se convertirá en la wiki de la comunidad y no ganará reputación y el voto UP ya no se computará más
Arthur Ronald
Vivir y aprender, supongo, tuve tantas ediciones, ya que seguía olvidándome de hacer una cosa u otra con mi entorno de prueba.
Justin
Lo mismo aquí, +1 para la investigación y una solución para su situación.
Pascal Thivent
¿Alguna posibilidad de solución con la versión 4.2.x Hibernate? Los componentes internos de Hibernate han cambiado un poco (comenté el problema referido: hibernate.atlassian.net/browse/HHH-5584 ).
Peter Butkovic
2

solucioné Mi problema agregando la anotación de @Lob que creará el byte [] en Oracle como blob, pero esta anotación creará el campo como oid que no funciona correctamente. Para hacer que el byte [] sea creado como bytea, hice el dialecto del cliente para 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

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

El Ghandor Yasser
fuente
0

Lo conseguí al anular la anotación con el 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 esta enidad problemática con el mapeo xml. Podemos anular entidades únicas / múltiples con mapeo xml. Por lo tanto, usaríamos anotaciones para nuestra base de datos con soporte principal y un archivo xml para cada otra base de datos.

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

Vinh Vo
fuente
0

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

@Lob
private String stringField;

y

@Lob
private byte[]   someByteStream;

Para arreglar lo anterior en postgres, he escrito a continuación 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 configure el dialecto personalizado en hibernación

hibernate.dialect=X.Y.Z.PostgreSQLDialectCustom   

XYZ es el nombre del paquete.

Ahora funciona bien. NOTA- Mi versión de Hibernate - 5.2.8.Versión final de Postgres- 9.6.3

Gajendra Kumar
fuente
0

Gracias Justin, Pascal por guiarme en la dirección correcta. También me enfrentaba al mismo problema con Hibernate 3.5.3. Su investigación y sus sugerencias sobre las clases adecuadas me ayudaron a identificar el problema y solucionarlo.

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

  1. Creé un BlobType personalizado que extendía MaterializedBlobType y anulaba el conjunto y los métodos de obtención 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 lograrlo.

      hibernateConfiguration= new AnnotationConfiguration();
      Mappings mappings = hibernateConfiguration.createMappings();
      mappings.addTypeDef("materialized_blob", "x.y.z.BlobType", null);
Nibin Jacob Panicker
fuente