如何使用Hibernate将数据流式传输到数据库BLOB(在byte 中没有内存存储)

我正在寻找一种方法将二进制数据流入/流出数据库。 如果可能的话,我希望用Hibernate完成(以数据库无关的方式)。 我发现的所有解决方案都涉及将二进制数据显式或隐式加载到内存中作为byte []。 我需要避免它。 假设我希望我的代码能够从数据库(存储在BLOB列中)向本地文件写入2GBvideo,或者相反,使用不超过256Mb的内存。 它显然是可以实现的,并且不涉及伏都教。 但是我找不到办法,现在我正试图避免调试Hibernate。

让我们看看示例代码(记住-Jmx = 256Mb)。

实体类:

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

Hibernate映射片段:

       

测试代码片段:

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

运行该代码片段时,我得到了OutOfMemoryexception。 查看堆栈跟踪显示了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将blob存储到数据库中时使用直接流来避免将流加载到内存中。 我想避免关于为什么将video存储在数据库列中而不是将其存储在某个内容存储库和链接中的主题。 请将其视为与问题无关的模型。

似乎在不同的方言上可能存在某种function,Hibernate可能会尝试将所有内容加载到内存中,因为底层数据库不支持流blob或类似的东西。 如果是这种情况 – 我希望在处理blob方面看到不同方言之间的某种比较表。

非常感谢您的帮助!

对于那些寻找相同的东西。

我的坏,代码的工作原理(流程而不试图复制到内存)PostgreSQL(可能还有很多其他的)。 Hibernate的内部工作取决于所选择的方言。 我在第一个地方使用的那个覆盖了直接使用流而支持由byte []支持的BinaryStream。

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

将数据存储在数据库中似乎比仅将其作为外部文件保存在磁盘上要慢。 但是在备份,处理特定文件系统的限制或并发更新等时,它可以为您节省很多麻烦。但这是一个偏离主题的问题。

您将Blob存储在POJO SimpleBean 。 这意味着如果blob大于您的堆空间,那么无论何时使用此对象或访问data字段,您都将获得OutOfMemoryError因为整个内容都被加载到内存中。

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

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

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

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

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

 java -Dhibernate.jdbc.use_streams_for_binary=true blobTest 

您可以在代码中certificate它已更改:

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