java - anotación adecuada de hibernación para byte
database hibernate (8)
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 oráculo.
@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ónBasic
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 implementaSerializable
. Como se describe en la Sección 2.8, el uso de la anotaciónBasic
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ónLob
al mapear a un tipo deLob
base de datos. La anotaciónLob
se puede usar junto con la anotación Básica o con la anotaciónElementCollection
cuando el valor de la colección de elementos es de tipo básico. UnLob
puede ser de tipo binario o de carácter. El tipo deLob
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 rama 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
<property name="hibernate.dialect" value="br.com.ar.dialect.CustomPostgreSQLDialect"/>
Y usa tu anotación portátil JPA @Lob
@Lob
public byte[] getValueBuffer() {
ACTUALIZAR
Aquí se ha extraído here
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 hibernar 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 here
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
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=X.Y.Z.PostgreSQLDialectCustom
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
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 frascos de hibernación.
Para ver más problemas de compatibilidad de Postgres / Oracle con Hibernate, consulte la publicación de mi blog .
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<byte[]> {
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 <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);
}
}
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.
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)); } }
Registre CustomBlobType con Hibernate. Lo siguiente es lo que hice para lograr eso.
hibernateConfiguration= new AnnotationConfiguration(); Mappings mappings = hibernateConfiguration.createMappings(); mappings.addTypeDef("materialized_blob", "x.y.z.BlobType", null);
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
Solucioné Mi problema agregando la anotación de @Lob que creará el byte [] en el oráculo 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