如何使用Hibernate在Oracle中保留大型BLOB(> 100MB)

pao*_*oyx 33 java oracle spring hibernate blob

我正在努力找到一种方法,使用BLOB列在我的Oracle数据库中插入大图像(> 100MB,主要是TIFF格式).

我已经在网络上进行了彻底搜索,甚至在StackOverflow中也没有找到这个问题的答案.
首先,问题......然后是相关代码(java类/配置)的一小部分,最后是第三部分,其中我展示了我写的测试图像持久性的junit测试(我在junit期间收到错误)测试执行)

编辑:我在问题的最后添加了一个部分,在那里我用JConsole描述了一些测试和分析

问题

java.lang.OutOfMemoryError: Java heap space使用hibernate 收到一个错误,并试图保留非常大的图像/文档:

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)
Run Code Online (Sandbox Code Playgroud)

代码(域对象,存储库类,配置)

这是我正在使用的一堆技术(从数据库到业务逻辑层).我使用JDK6.

  • Oracle数据库10g企业版10.2.0.4.0版 - 产品介绍
  • ojdbc6.jar(适用于11.2.0.3版本)
  • Hibernate 4.0.1 Final
  • 春季3.1.GA发布

我有两个域类,以一对多的方式映射.A DocumentVersion有很多DocumentData,每个都可以代表不同的二进制内容DocumentVersion.

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;
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

这是我的Spring和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" />
Run Code Online (Sandbox Code Playgroud)

我的数据源定义:

<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>
Run Code Online (Sandbox Code Playgroud)

属性来自这里:

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
Run Code Online (Sandbox Code Playgroud)

我有一个服务类,它委托给一个存储库类:

@Transactional
public class DocumentManagerImpl implements DocumentManager {

DocumentVersionDao documentVersionDao;

public void setDocumentVersionDao(DocumentVersionDao documentVersionDao) {
    this.documentVersionDao = documentVersionDao;
}
Run Code Online (Sandbox Code Playgroud)

现在来自存储库类的相关摘录:

public class DocumentVersionDaoHibernate implements DocumentVersionDao {

@Autowired
private SessionFactory sessionFactory;

@Override
public DocumentVersion saveOrUpdate(DocumentVersion record) {
    this.sessionFactory.getCurrentSession().saveOrUpdate(record);
    return record;
}
Run Code Online (Sandbox Code Playgroud)

导致错误的JUnit测试

如果我运行以下单元测试我有上述错误(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());
}
Run Code Online (Sandbox Code Playgroud)

相同的代码工作对一个PostreSQL 9安装.图像正在数据库中写入.调试我的代码,我已经能够发现PostgreSQL jdbc驱动程序使用缓冲的输出流写入数据库....而Oracle OJDBC驱动程序尝试一次性分配所有byte[]代表图像.

从错误堆栈:

java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2786)
at java.io.ByteArrayOutputStream.toByteArray(ByteArrayOutputStream.java:133)
Run Code Online (Sandbox Code Playgroud)

是否由于此行为导致错误?谁能给我一些关于这个问题的见解?

感谢大家.

使用JConsole进行内存测试

感谢收到的关于我的问题的建议,我试图做一些简单的测试来显示我的代码的内存使用情况,使用两个不同的jdbc驱动程序,一个用于PostgreSQL,一个用于Oracle.测试设置:

  1. 测试是使用上一节中描述的JUnit测试进行的.
  2. JVM堆大小已设置为512MB,使用参数-Xmx512MB
  3. 对于Oracle数据库,我使用了ojdbc6.jar驱动程序
  4. 对于Postgres数据库,我使用了9.0-801.jdbc3驱动程序(通过Maven)

首次测试,文件大约150MB

在第一次测试中,Oracle和Postgres都通过了测试(这是大新闻).该文件的大小为可用JVM堆大小的1/3.这里是JVM内存消耗的图片:

测试Oracle,512MB堆大小,150MB文件 测试Oracle,512MB堆大小,150MB文件

测试PostgreSQL,512MB堆大小,150MB文件 测试PostgreSQL,512MB堆大小,150MB文件

第二次测试,文件大约485MB

在第二次测试中,只有Postgres通过了测试而Oracle 失败了.该文件的大小非常接近可用JVM堆空间的大小.这里是JVM内存消耗的图片:

测试Oracle,512MB堆大小,485MB文件 测试Oracle,512MB堆大小,485MB文件

测试PostgreSQL,512MB堆大小,485MB文件 测试PostgreSQL,512MB堆大小,485MB文件

分析测试:

似乎PostgreSQL驱动程序处理内存时没有超过某个阈值,而Oracle驱动程序的行为却截然不同.

我无法诚实地解释为什么Oracle jdbc驱动程序java.lang.OutOfMemoryError: Java heap space在使用可用堆空间附近的文件大小时会导致错误(相同).

有没有人可以给我更多的见解?非常感谢你的帮助:)

mek*_*e84 6

在尝试使用"blob"类型进行映射时,我遇到了与您相同的问题.这是我在hibernate网站上发布的帖子的链接:https://forum.hibernate.org/viewtopic.php?p = 2452481#p2452481

Hibernate 3.6.9
Oracle驱动程序11.2.0.2.0
Oracle数据库11.2.0.2.0

为了解决这个问题,我使用了具有Blob的自定义UserType的代码,我的返回类型是java.sql.Blob.

以下是此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);
   }
}
Run Code Online (Sandbox Code Playgroud)


Łuk*_*nek 4

就我个人而言,我使用 Hibernate 在 Oracle BLOB 列中存储最多 200MB 的文件,因此我可以确保它可以正常工作。所以...

您应该尝试更新版本的 Oracle JDBC 驱动程序。似乎随着时间的推移,使用字节数组而不是流的这种行为发生了一些变化。并且驱动程序向后兼容。我不确定这是否能解决您的问题,但它对我有用。另外,您应该切换到 org.hibernate.dialect.Oracle10gDialect - 它不再使用 oracle.jdbc.driver 包,转而使用 oracle.jdbc - 它也可能有帮助。