2012-08-12 24 views
7

Estoy buscando una manera de transmitir datos binarios a/desde la base de datos. Si es posible, me gustaría que fuera hecho con Hibernate (en una base de datos de manera independiente). Todas las soluciones que he encontrado implican la carga explícita o implícita de datos binarios en la memoria como byte []. Necesito evitarlo. Digamos que quiero que mi código pueda escribir en un archivo local un video de 2GB de la base de datos (almacenado en la columna BLOB), o al revés, usando no más de 256Mb de memoria. Es claramente alcanzable, y no implica vudú. Pero no puedo encontrar la manera, por ahora estoy tratando de evitar la depuración de Hibernate.Cómo transmitir datos a la base de datos BLOB usando Hibernate (sin almacenamiento en memoria en byte [])

Miremos el código de muestra (teniendo en cuenta -Jmx = 256Mb).

clase Entidad:

public class SimpleBean { 
    private Long id; 
    private Blob data; 
    // ... skipping getters, setters and constructors. 
} 

Hibernate fragmento de mapeo:

<class name="SimpleBean" table="SIMPLE_BEANS"> 
    <id name="id" column="SIMPLE_BEAN_ID"> 
     <generator class="increment" /> 
    </id> 
    <property name="data" type="blob" column="DATA" /> 
</class> 

prueba fragmento de código:

Configuration cfg = new Configuration().configure("hibernate.cfg.xml"); 
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder() 
             .applySettings(cfg.getProperties()) 
             .buildServiceRegistry(); 

SessionFactory sessionFactory = cfg.buildSessionFactory(serviceRegistry); 
Session session = sessionFactory.openSession(); 
session.beginTransaction(); 

File dataFile = new File("movie_1gb.avi"); 
long dataSize = dataFile.length(); 
InputStream dataStream = new FileInputStream(dataFile); 

LobHelper lobHelper = session.getLobHelper(); 
Blob dataBlob = lobHelper.createBlob(dataStream, dataSize); 

session.save(new SimpleBean(data)); 
session.getTransaction().commit(); // Throws java.lang.OutOfMemoryError 
session.close(); 

blobStream.close(); 
sessionFactory.close(); 

Cuando se ejecuta fragmento que llego excepción OutOfMemory. Observar el seguimiento de la pila muestra qué Hibernate intenta cargar la secuencia en la memoria y obtiene OutOfMemory (como debería). Aquí está el seguimiento de la pila:

java.lang.OutOfMemoryError: Java heap space 
at java.util.Arrays.copyOf(Arrays.java:2271) 
at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:113) 
at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93) 
at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:140) 
at org.hibernate.type.descriptor.java.DataHelper.extractBytes(DataHelper.java:183) 
at org.hibernate.type.descriptor.java.BlobTypeDescriptor.unwrap(BlobTypeDescriptor.java:121) 
at org.hibernate.type.descriptor.java.BlobTypeDescriptor.unwrap(BlobTypeDescriptor.java:45) 
at org.hibernate.type.descriptor.sql.BlobTypeDescriptor$4$1.doBind(BlobTypeDescriptor.java:105) 
at org.hibernate.type.descriptor.sql.BasicBinder.bind(BasicBinder.java:92) 
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:305) 
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:300) 
at org.hibernate.type.AbstractSingleColumnStandardBasicType.nullSafeSet(AbstractSingleColumnStandardBasicType.java:57) 
at org.hibernate.persister.entity.AbstractEntityPersister.dehydrate(AbstractEntityPersister.java:2603) 
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2857) 
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3301) 
at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:88) 
at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:362) 
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:354) 
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:275) 
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:326) 
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:52) 
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1214) 
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:403) 
at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101) 
at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:175) 
at ru.swemel.msgcenter.domain.SimpleBeanTest.testBasicUsage(SimpleBeanTest.java:63) 
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) 
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45) 
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) 

Usado Hibernate 4.1.5.SP1. La pregunta exacta es: cómo evitar cargar flujo en la memoria cuando se almacena un blob en la base de datos usando Hibernate, usando transmisión directa en su lugar. Me gustaría evitar los temas sobre por qué uno almacena el video en la columna de la base de datos en lugar de almacenarlo en algún repositorio de contenido y vincularlo. Por favor, considere un modelo lo que es irrelevante para la pregunta.

Parece que podría haber algún tipo de capacidades en diferentes dialectos e Hibernate podría intentar cargar todo en la memoria, porque la base de datos subyacente no es compatible con streaming blobs o algo así. Si es el caso, me gustaría ver algún tipo de tabla comparativa entre diferentes dialectos en el aspecto del manejo de blobs.

¡Muchas gracias por su ayuda!

Respuesta

4

Para aquellos que buscan lo mismo.

Malo, el código funciona como se supone que (secuencias sin intentar copiar a la memoria) para PostgreSQL (y probablemente muchas otras). El trabajo interno de Hibernate depende del dialecto seleccionado. El que utilicé en primer lugar anula el uso directo de las secuencias a favor de BinaryStream respaldado por byte [].

Además, no hay problemas de rendimiento, ya que solo carga OID (número) en el caso de PostgreSQL, y probablemente cargas perezosas de datos en el caso de otros dialectos (incluida la implementación de byte []). Acabo de ejecutar algunas pruebas sucias, sin diferencias visibles en 10000 cargas de entidad con y sin campo de datos binarios.

de datos Almacenamiento de la base de datos parece ser más lento que acaba de guardarlo en el disco como archivo externo sin embargo. Pero le ahorra muchos dolores de cabeza al hacer copias de seguridad, o al lidiar con limitaciones de un sistema de archivos en particular, o actualizaciones concurrentes, etc. Pero es un tema fuera de lugar.

+0

Save yourse Si alguna vez, no intente evitar la depuración.) – IceGlow

2

Está almacenando Blob en su POJO SimpleBean. Esto significa que si el blob es más grande que su espacio de montón, cada vez que trabaje con este objeto o acceda al campo data, obtendrá el OutOfMemoryError porque todo está cargado en la memoria.

No creo que haya una manera de establecer u obtener un campo de base de datos utilizando un Stream en hibernación, y HQL solo inserta en sentencias SELECT.

Lo que debe hacer es eliminar el campo data del objeto SimpleBean para que no se almacene en la memoria cuando carga o guarda. Pero cuando necesite guardar un blob, puede usar el save() de hibernate para crear la fila, luego use un jdbc PreparedStatement y el método setBinaryStream(). Cuando necesite acceder a la transmisión, puede usar el método load() de hibernate para obtener un objeto SimpleBean y hacer una selección de jdbc para obtener un ResultSet y luego usar el método getBinaryStream() para leer el blob. Los documentos para setBinaryStream() dicen:

Los datos se leerán de la secuencia según sea necesario hasta que se alcance el final del archivo.

Por lo tanto, los datos no se almacenarán por completo en la memoria.

+0

Gracias por responder Sí, estoy buscando el comportamiento PreparedStatement.setBinaryStream pero yo soy la esperanza de encontrar una forma de abstracción de base de datos subyacente, si es posible Algunos RDBMS tiene tipo BLOB para!.. columnas, PostgreSQL almacena BLOBs por separado y los referencia por OID, etc. – IceGlow

2

Su solución usando el lobHelper de Hibernate debería funcionar, pero es posible que deba asegurarse de que se aplique el uso de las transmisiones. propiedad Conjunto hibernate.jdbc.use_streams_for_binary = true Esta es una propiedad a nivel de sistema, por lo que tiene que ser establecido en el arranque (la he definido en la línea de comandos durante la prueba:

java -Dhibernate.jdbc.use_streams_for_binary=true blobTest 

se puede demostrar que ha cambiado en el código:

Object prop = props.get("hibernate.jdbc.use_streams_for_binary"); 
System.out.println("hibernate.jdbc.use_streams_for_binary" + "/" + prop); 
+0

¿Es esto para cualquier base de datos u Oracle solamente? – Nurlan

Cuestiones relacionadas