2012-08-12 114 views
7

我正在寻找将二进制数据传输到数据库/从数据库传输数据的方法。如果可能的话,我想用Hibernate来完成(以数据库不可知的方式)。 我发现的所有解决方案都包括将二进制数据以byte []形式显式或隐式加载到内存中。我需要避免它。比方说,我希望我的代码能够从数据库(存储在BLOB列中)向本地文件写入2GB视频,或者反过来,使用不超过256Mb的内存。这显然是可以实现的,并且不涉及巫毒。但我找不到方法,现在我试图避免调试Hibernate。我们来看看示例代码(记住-Jmx = 256Mb)。如何使用Hibernate将数据流式传输到数据库BLOB(无内存中存储在字节[]中)

实体类:

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

Hibernate映射片段:

<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> 

测试的代码片段:

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(); 

如果运行的片段,我得到了OutOfMemory例外。查看堆栈跟踪显示了Hibernate尝试将内容加载到内存中并获取OutOfMemory(应该如此)。这里的堆栈跟踪:

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) 

用过的Hibernate 4.1.5.SP1。确切的问题是:如何避免在使用Hibernate将数据库存储在数据库中时将流加载到内存中,而不是直接使用流。我想避开关于为什么将视频存储在数据库列中而不是将其存储在某个内容存储库和链接中的主题。请认为它是一个与问题无关的模型。

似乎在不同的方言可能有某种功能,Hibernate可能会尝试将所有内容加载到内存中,因为底层数据库不支持流式blob或类似的东西。如果是这样的话 - 我想在处理斑点方面看到不同方言之间的某种比较表。

非常感谢您的帮助!

回答

4

对于那些寻找同样的事情。

我的糟糕的是,代码的工作原理应该是(为流而不尝试复制到内存)PostgreSQL(可能还有很多其他)。 Hibernate的内部工作取决于选定的方言。我首先使用的那个覆盖了直接使用流,支持由byte []支持的BinaryStream。

此外,性能没有问题,因为它在PostgreSQL的情况下仅加载OID(数字),并且在其他方​​言(包括byte []实现)的情况下可能延迟加载数据。刚刚运行了一些脏测试,在没有二进制数据字段的10000个实体负载中没有可见的差异。

将数据存储在数据库中似乎比将其作为外部文件保存在磁盘上要慢一些。但是在备份时或者处理特定文件系统的限制或并发更新等时,它可以为您节省大量的头痛。但这是一个无关紧要的问题。

+0

Save yourrse如果有一段时间,不要试图避免调试。 ) – IceGlow 2012-08-14 06:46:40

2

您正在将Blob存储在您的POJO SimpleBean中。这意味着如果blob大于堆空间,则只要您使用此对象或访问data字段,您将获得OutOfMemoryError,因为整个事件都会加载到内存中。

我不认为有一种方法可以在hibernate中使用Stream设置或获取数据库字段,并且HQL仅插入到SELECT语句中。

您可能需要做的是从SimpleBean对象中删除data字段,以便在加载或保存时不会将其存储在内存中。但是当你需要保存一个blob时,你可以使用hibernate的save()来创建该行,然后使用jdbc PreparedStatementsetBinaryStream()方法。当您需要访问流时,可以使用hibernate的load()方法获取SimpleBean对象,并执行jdbc select以获取ResultSet,然后使用getBinaryStream()方法读取blob。 setBinaryStream()的文档说:

数据将根据需要从流中读取,直到达到文件结束为止。

因此,数据将不会完全存储在内存中。

+0

感谢您的回答是的,我正在寻找PreparedStatement.setBinaryStream的行为,但我希望如果有可能找到一种方法来抽象从底层数据库中的一些RDBMS中有BLOB类型!。列,PostgreSQL分别存储BLOB并通过OID引用它们等。 – IceGlow 2012-08-13 08:33:57

2

您使用Hibernate的lobHelper的解决方案应该可行,但您可能需要确保强制使用流。 设置属性hibernate.jdbc.use_streams_for_binary在= TRUE 这是一个系统级的属性,因此它必须在启动时(我在测试过程中定义它的命令行设置:

java -Dhibernate.jdbc.use_streams_for_binary=true blobTest 

你能证明它改变在你的代码:

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

这仅适用于任何数据库或Oracle吗? – Nurlan 2016-02-29 09:28:51

相关问题