ver update manejo longitud insertar dato como campo java oracle hibernate spring blob

java - update - Cómo persistir BLOB GRANDES(> 100MB) en Oracle usando Hibernate



manejo de blob en oracle (5)

¿Has intentado definir LobHandler y su versión para oráculo OracleLobHandler en tu fábrica de sesiones?

Aquí hay un ejemplo:

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="oracleDocDataSource"/> <property name="annotatedClasses"> <list> ... </list> </property> <property name="lobHandler"> <bean class="org.springframework.jdbc.support.lob.OracleLobHandler"> <property name="nativeJdbcExtractor"> <bean class="org.springframework.jdbc.support.nativejdbc.WebSphereNativeJdbcExtractor"/> </property> </bean> </property> </bean>

ACTUALIZAR

Me acabo de dar cuenta de que el discurso es sobre Hibernate 4.

Estoy luchando por encontrar una forma de insertar imágenes GRANDES (> 100MB, en su mayoría formato TIFF) en mi base de datos Oracle, usando columnas BLOB.

He buscado a fondo en la web e incluso en StackOverflow, sin poder encontrar una respuesta a este problema.
En primer lugar, el problema ... luego una breve sección sobre el código relevante (clases / configuración java), finalmente una tercera sección donde muestro la prueba junit que he escrito para probar la persistencia de la imagen (recibo el error durante mi junit ejecución de pruebas)

Editar: agregué una sección al final de la pregunta, donde describí algunas pruebas y análisis usando JConsole.

El problema

Recibo un error java.lang.OutOfMemoryError: Java heap space usando hibernate y tratando de conservar imágenes / documentos muy grandes:

java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:2786) at java.io.ByteArrayOutputStream.toByteArray(ByteArrayOutputStream.java:133) at org.hibernate.type.descriptor.java.DataHelper.extractBytes(DataHelper.java:190) at org.hibernate.type.descriptor.java.BlobTypeDescriptor.unwrap(BlobTypeDescriptor.java:123) at org.hibernate.type.descriptor.java.BlobTypeDescriptor.unwrap(BlobTypeDescriptor.java:47) at org.hibernate.type.descriptor.sql.BlobTypeDescriptor$4$1.doBind(BlobTypeDescriptor.java:101) at org.hibernate.type.descriptor.sql.BasicBinder.bind(BasicBinder.java:91) at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:283) at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:278) at org.hibernate.type.AbstractSingleColumnStandardBasicType.nullSafeSet(AbstractSingleColumnStandardBasicType.java:89) at org.hibernate.persister.entity.AbstractEntityPersister.dehydrate(AbstractEntityPersister.java:2184) at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2430) at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2874) at org.hibernate.action.EntityInsertAction.execute(EntityInsertAction.java:79) at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:273) at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:265) at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:184) at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321) at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51) at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216) at it.paoloyx.blobcrud.manager.DocumentManagerTest.testInsertDocumentVersion(DocumentManagerTest.java:929)

El código (objetos de dominio, clases de repositorio, configuración)

Aquí está la pila de tecnologías que estoy usando (de DB a nivel de lógica de negocios). Yo uso JDK6.

  • Oracle Database 10g Enterprise Edition versión 10.2.0.4.0 - Prod
  • ojdbc6.jar (para la versión 11.2.0.3)
  • Hibernate 4.0.1 Final
  • Primavera 3.1.GA LIBERACIÓN

Tengo dos clases de dominio, mapeadas de manera uno a muchos. Una DocumentVersion tiene muchos DocumentData , cada uno de ellos puede representar diferentes contenidos binarios para la misma DocumentVersion .

Extracto relevante de DocumentVersion clase DocumentVersion :

@Entity @Table(name = "DOCUMENT_VERSION") public class DocumentVersion implements Serializable { private static final long serialVersionUID = 1L; private Long id; private Set<DocumentData> otherDocumentContents = new HashSet<DocumentData>(0); @Id @GeneratedValue(strategy = GenerationType.TABLE) @Column(name = "DOV_ID", nullable = false) public Long getId() { return id; } @OneToMany @Cascade({ CascadeType.SAVE_UPDATE }) @JoinColumn(name = "DOD_DOCUMENT_VERSION") public Set<DocumentData> getOtherDocumentContents() { return otherDocumentContents; }

Extracto relevante de DocumentData clase DocumentData :

@Entity @Table(name = "DOCUMENT_DATA") public class DocumentData { private Long id; /** * The binary content (java.sql.Blob) */ private Blob binaryContent; @Id @GeneratedValue(strategy = GenerationType.TABLE) @Column(name = "DOD_ID", nullable = false) public Long getId() { return id; } @Lob @Column(name = "DOD_CONTENT") public Blob getBinaryContent() { return binaryContent; }

Aquí están mis parámetros principales de configuración de Spring e Hibernate:

<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="packagesToScan" value="it.paoloyx.blobcrud.model" /> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop> <prop key="hibernate.hbm2ddl.auto">create</prop> <prop key="hibernate.jdbc.batch_size">0</prop> <prop key="hibernate.jdbc.use_streams_for_binary">true</prop> </props> </property> </bean> <bean class="org.springframework.orm.hibernate4.HibernateTransactionManager" id="transactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <tx:annotation-driven transaction-manager="transactionManager" />

Mi definición de fuente de datos:

<bean class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" id="dataSource"> <property name="driverClassName" value="${database.driverClassName}" /> <property name="url" value="${database.url}" /> <property name="username" value="${database.username}" /> <property name="password" value="${database.password}" /> <property name="testOnBorrow" value="true" /> <property name="testOnReturn" value="true" /> <property name="testWhileIdle" value="true" /> <property name="timeBetweenEvictionRunsMillis" value="1800000" /> <property name="numTestsPerEvictionRun" value="3" /> <property name="minEvictableIdleTimeMillis" value="1800000" /> <property name="validationQuery" value="${database.validationQuery}" /> </bean>

donde las propiedades se toman desde aquí:

database.driverClassName=oracle.jdbc.OracleDriver database.url=jdbc:oracle:thin:@localhost:1521:devdb database.username=blobcrud database.password=blobcrud database.validationQuery=SELECT 1 from dual

Tengo una clase de servicio, que delega en una clase de repositorio:

@Transactional public class DocumentManagerImpl implements DocumentManager { DocumentVersionDao documentVersionDao; public void setDocumentVersionDao(DocumentVersionDao documentVersionDao) { this.documentVersionDao = documentVersionDao; }

y ahora los extractos relevantes de las clases de repositorio:

public class DocumentVersionDaoHibernate implements DocumentVersionDao { @Autowired private SessionFactory sessionFactory; @Override public DocumentVersion saveOrUpdate(DocumentVersion record) { this.sessionFactory.getCurrentSession().saveOrUpdate(record); return record; }

La prueba JUnit que causa el error

Si ejecuto la siguiente prueba unitaria, tengo el error antes mencionado ( java.lang.OutOfMemoryError: Java heap space ):

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath*:META-INF/spring/applicationContext*.xml" }) @Transactional public class DocumentManagerTest { @Autowired protected DocumentVersionDao documentVersionDao; @Autowired protected SessionFactory sessionFactory; @Test public void testInsertDocumentVersion() throws SQLException { // Original mock document content DocumentData dod = new DocumentData(); // image.tiff is approx. 120MB File veryBigFile = new File("/Users/paoloyx/Desktop/image.tiff"); try { Session session = this.sessionFactory.getCurrentSession(); InputStream inStream = FileUtils.openInputStream(veryBigFile); Blob blob = Hibernate.getLobCreator(session).createBlob(inStream, veryBigFile.length()); dod.setBinaryContent(blob); } catch (IOException e) { e.printStackTrace(); dod.setBinaryContent(null); } // Save a document version linked to previous document contents DocumentVersion dov = new DocumentVersion(); dov.getOtherDocumentContents().add(dod); documentVersionDao.saveOrUpdate(dov); this.sessionFactory.getCurrentSession().flush(); // Clear session, then try retrieval this.sessionFactory.getCurrentSession().clear(); DocumentVersion dbDov = documentVersionDao.findByPK(insertedId); Assert.assertNotNull("Il document version ritornato per l''id " + insertedId + " è nullo", dbDov); Assert.assertNotNull("Il document version recuperato non ha associato contenuti aggiuntivi", dbDov.getOtherDocumentContents()); Assert.assertEquals("Il numero di contenuti secondari non corrisponde con quello salvato", 1, dbDov.getOtherDocumentContents().size()); }

El mismo código funciona en contra de una instalación de PostreSQL 9. Las imágenes se escriben en la base de datos. Depurando mi código, he podido encontrar que los controladores jdbc de PostgreSQL escriben en la base de datos utilizando una secuencia de salida almacenada ... mientras que el controlador OJDBC de Oracle intenta asignar todos a la vez todo el byte[] representa la imagen.

Desde la pila de errores:

java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:2786) at java.io.ByteArrayOutputStream.toByteArray(ByteArrayOutputStream.java:133)

¿El error se debe a este comportamiento? ¿Alguien puede darme algunas ideas sobre este problema?

Gracias a todos.

Pruebas de memoria con JConsole

Gracias a las sugerencias recibidas para mi pregunta, intenté hacer algunas pruebas simples para mostrar el uso de memoria de mi código utilizando dos controladores jdbc diferentes, uno para PostgreSQL y otro para Oracle. Configuración de prueba:

  1. La prueba se ha llevado a cabo utilizando la prueba JUnit descrita en la sección anterior.
  2. El tamaño de almacenamiento JVM se ha establecido en 512 MB, usando el parámetro -Xmx512MB
  3. Para la base de datos Oracle, he usado el controlador ojdbc6.jar
  4. Para la base de datos de Postgres, he usado el controlador 9.0-801.jdbc3 (a través de Maven)

Primera prueba, con un archivo de aproximadamente 150 MB

En esta primera prueba, tanto Oracle como Postgres pasaron la prueba (esta es una noticia GRANDE). El archivo tiene un tamaño de 1/3 del tamaño de pila de JVM disponible. Aquí la imagen del consumo de memoria JVM:

Probando Oracle, tamaño del montón de 512 MB, archivo de 150 MB

Prueba de PostgreSQL, tamaño del montón de 512 MB, archivo de 150 MB

Segunda prueba, con un archivo de aproximadamente 485 MB

En esta segunda prueba, solo Postgres pasó la prueba y Oracle falló . El archivo tiene un tamaño muy parecido al tamaño del espacio de almacenamiento JVM disponible. Aquí la imagen del consumo de memoria JVM:

Probando Oracle, 512MB Heap Size, archivo de 485MB

Prueba de PostgreSQL, tamaño del montón de 512 MB, archivo de 485 MB

Análisis de las pruebas:

Parece que el controlador PostgreSQL maneja la memoria sin sobrepasar cierto umbral, mientras que el controlador Oracle se comporta de manera muy diferente.

Honestamente, no puedo explicar por qué el controlador jdbc de Oracle me lleva a un error (el mismo java.lang.OutOfMemoryError: Java heap space ) cuando se usa un archivo de tamaño cerca del espacio de almacenamiento dinámico disponible.

¿Hay alguien que pueda darme más información? Muchas gracias por su ayuda :)


Acabo de descubrir esta pregunta cuando tuve el mismo problema con Oracle e Hibernate. El problema está en el manejo de blobs de Hibernate. Parece copiar el blob a la memoria dependiendo del Dialecto en uso. Supongo que lo hacen, porque es requerido por algunas bases de datos / controladores. Para Oracle, sin embargo, este comportamiento no parece ser necesario.

La solución es bastante simple, solo crea un OracleDialect personalizado que contenga este código:

public class Oracle10DialectWithoutInputStreamToInsertBlob extends Oracle10gDialect { public boolean useInputStreamToInsertBlob() { return false; } }

A continuación, debe configurar su fábrica de sesión para usar este Dialecto. Lo probé con el controlador ojdbc6-11.2.0.1.0 hacia Oracle 11g y confirmó que esto soluciona el problema con el consumo de memoria.

Si alguno de ustedes intenta esto con otra base de datos Oracle y / o con un controlador Oracle diferente, me encantaría saber si funciona para usted. Si funciona con varias configuraciones, enviaré una solicitud de extracción al equipo de Hibernate.


Estaba teniendo los mismos problemas que usted al intentar asignar el tipo de "blob". Aquí hay un enlace a una publicación que hice en el sitio de hibernación: https://forum.hibernate.org/viewtopic.php?p=2452481#p2452481

Hibernate 3.6.9
Controlador Oracle 11.2.0.2.0
Oracle Database 11.2.0.2.0

Para solucionar el problema, utilicé un código que tenía un UserType personalizado para Blob. Tuve el tipo de devolución java.sql.Blob.

Estas son las implementaciones de métodos clave de este UserType:

public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException { Blob blob = rs.getBlob(names[0]); if (blob == null) return null; return blob; } public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException { if (value == null) { st.setNull(index, sqlTypes()[0]); } else { InputStream in = null; OutputStream out = null; // oracle.sql.BLOB BLOB tempBlob = BLOB.createTemporary(st.getConnection(), true, BLOB.DURATION_SESSION); tempBlob.open(BLOB.MODE_READWRITE); out = tempBlob.getBinaryOutputStream(); Blob valueAsBlob = (Blob) value; in = valueAsBlob.getBinaryStream(); StreamUtil.toOutput(in, out); out.flush(); StreamUtil.close(out); tempBlob.close(); st.setBlob(index, tempBlob); StreamUtil.close(in); } }


No es la mejor solución, pero puedes permitir que Java use más memoria con -Xmx parametr

Editar: Intente analizar el problema más profundamente, intente utilizar JConsole . Te ayuda a ver la carga de memoria.

Incluso con Postgres, puede obtener el límite de tamaño de almacenamiento dinámico, pero no cruzarlo porque el controlador cargado consume menos memoria.

Con las configuraciones predeterminadas, su límite de tamaño de heam es aproximadamente la mitad de su memoria física. Prueba cuánto blob más grande puedes guardar en postgres.


Personalmente almaceno archivos de hasta 200MB en columnas Oracle BLOB usando Hibernate, así puedo asegurar que funcione. Asi que...

Debería probar una versión más nueva del controlador Oracle JDBC. Parece que este comportamiento de usar matrices de bytes en lugar de secuencias ha cambiado un poco con el tiempo. Y los controladores son compatibles con versiones anteriores. No estoy seguro, si eso solucionará tu problema, pero funciona para mí. Además, debe cambiar a org.hibernate.dialect.Oracle10gDialect, que retira el uso del paquete oracle.jdbc.driver a favor de oracle.jdbc, y también podría ayudar.