【发布时间】:2012-03-04 10:19:15
【问题描述】:
我正在努力寻找一种使用 BLOB 列在我的 Oracle 数据库中插入 LARGE 图像(>100MB,主要是 TIFF 格式)的方法。
我已经在整个网络甚至 StackOverflow 中进行了彻底的搜索,但无法找到这个问题的答案。
首先,问题...然后是相关代码的一小部分(java 类/配置),最后是 第三部分,我在其中展示了我为测试图像持久性而编写的 junit 测试(我在 junit 测试执行期间收到错误)
编辑:我在问题末尾添加了一个部分,我在其中描述了一些使用 JConsole 进行的测试和分析
问题
我在使用休眠时收到java.lang.OutOfMemoryError: Java heap space 错误并尝试保留非常大的图像/文档:
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)
代码(域对象、存储库类、配置)
这是我正在使用的技术堆栈(从数据库到业务逻辑层)。我用的是JDK6。
- Oracle 数据库 10g 企业版版本 10.2.0.4.0 - 产品
- ojdbc6.jar(适用于 11.2.0.3 版本)
- 休眠 4.0.1 最终版
- 春季 3.1.GA 发布
我有两个域类,以一对多的方式映射。一个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;
}
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;
}
这是我的 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" />
我的数据源定义:
<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>
属性取自这里:
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
我有一个服务类,它委托给存储库类:
@Transactional
public class DocumentManagerImpl implements DocumentManager {
DocumentVersionDao documentVersionDao;
public void setDocumentVersionDao(DocumentVersionDao documentVersionDao) {
this.documentVersionDao = documentVersionDao;
}
现在是存储库类的相关摘录:
public class DocumentVersionDaoHibernate implements DocumentVersionDao {
@Autowired
private SessionFactory sessionFactory;
@Override
public DocumentVersion saveOrUpdate(DocumentVersion record) {
this.sessionFactory.getCurrentSession().saveOrUpdate(record);
return record;
}
导致错误的 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());
}
相同的代码适用于 PostreSQL 9 安装。图像正在写入数据库中。
调试我的代码,我发现 PostgreSQL jdbc 驱动程序使用缓冲输出流写入数据库......而 Oracle OJDBC 驱动程序尝试一次分配所有 byte[]representing 图像。
来自错误堆栈:
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2786)
at java.io.ByteArrayOutputStream.toByteArray(ByteArrayOutputStream.java:133)
错误是由于这种行为造成的吗? 谁能给我一些关于这个问题的见解?
谢谢大家。
使用 JConsole 进行内存测试
感谢收到的针对我的问题的建议,我尝试使用两种不同的 jdbc 驱动程序进行一些简单的测试,以显示我的代码的内存使用情况,一种用于 PostgreSQL,另一种用于 Oracle。 测试设置:
- 已使用上一节中描述的 JUnit 测试进行了测试。
- JVM 堆大小已设置为 512MB,使用参数 -Xmx512MB
- 对于 Oracle 数据库,我使用了 ojdbc6.jar 驱动程序
- 对于 Postgres 数据库,我使用了 9.0-801.jdbc3 驱动程序(通过 Maven)
第一次测试,文件大约 150MB
在第一次测试中,Oracle 和 Postgres通过了测试(这是个大新闻)。 该文件的大小是可用 JVM 堆大小的 1/3。 这是JVM内存消耗的图片:
测试 Oracle,512MB 堆大小,150MB 文件
测试 PostgreSQL,512MB 堆大小,150MB 文件
第二次测试,文件约485MB
在第二次测试中,只有 Postgres 通过了测试,而 Oracle 失败了。 该文件的大小非常接近可用 JVM 堆空间的大小。 这是JVM内存消耗的图片:
测试 Oracle,512MB 堆大小,485MB 文件
测试 PostgreSQL,512MB 堆大小,485MB 文件
测试分析:
似乎 PostgreSQL 驱动程序处理内存没有超过某个阈值,而 Oracle 驱动程序的行为非常不同。
当使用大小接近可用堆空间的文件时,我无法诚实地解释为什么 Oracle jdbc 驱动程序会导致我出错(同样的 java.lang.OutOfMemoryError: Java heap space)。
有没有人可以给我更多的见解? 非常感谢您的帮助:)
【问题讨论】:
-
+1 用于单元测试。看起来问题不像 Oracle 驱动程序那么严重......是否有任何替代的 Oracle JDBC 驱动程序可能不会那么脑残?
-
使用原始 JDBC 并绕过 Hibernate/JPA 进行此操作。 JDBC 允许将
InputStreams 设置为参数(不是完美无缺,但它可以工作)。我真的希望有一个更好的答案,但是我没有找到任何与 Hibernate/JPA/Oracle 配合得很好的东西。 -
他的,感谢您的评论。我已经编写了一个将大图像存储在数据库中的工作类,但是使用普通 JDBC 确实是我想留下的一个选项,作为我的最终选择。我真的很想找到一种使用 Hibernate 的方法......
-
非常感谢您的 +1,乔。我不确定是否有任何替代的 oracle-jdbc 驱动程序......也许不包括 DataDirect 驱动程序,但我不期待使用它们。您认为我应该购买许可证并试用吗?
-
OutOfMemoryError 由 JVM 抛出,因为它决定它不能再使用您获得的设置(eden,tenured)增加堆大小。您可能知道,JVM 至少有 9 个参数来调整 Sun/Oracle JVM 中的堆大小(在 JRockit 中少一些)。所以这就是为什么它比你认为它应该更快地抛出 OutOfMemoryError 的原因。另一方面,上面的图表清楚地表明 PostgreSQL 驱动程序没有让 Hibernate 在上传过程中创建完整的数组。通常我的下一步是验证调用的堆栈跟踪,以查看 Hibernate 在做什么以及为什么。
标签: java oracle hibernate spring blob