12

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

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

实体类:

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

休眠映射片段:

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

使用休眠 4.1.5.SP1。确切的问题是:当使用 Hibernate 在数据库中存储 blob 时,如何避免将流加载到内存中,而不是使用直接流。我想避免关于为什么将视频存储在数据库列中而不是将其存储在某些内容存储库和链接中的话题。请考虑将其视为与问题无关的模型。

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

非常感谢您的帮助!

4

3 回答 3

5

对于那些寻找相同事物的人。

我的错,对于 PostgreSQL(可能还有很多其他人),代码按预期工作(流而不试图复制到内存)。Hibernate 的内部工作取决于选择的方言。我首先使用的那个覆盖了直接使用流,支持由 byte[] 支持的 BinaryStream。

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

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

于 2012-08-14T06:44:16.853 回答
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);
于 2015-06-26T10:23:29.497 回答
1

您将 存储Blob在您的 POJOSimpleBean中。这意味着如果 blob 大于您的堆空间,则无论何时使用此对象或访问该data字段,您都会得到 ,OutOfMemoryError因为整个事物已加载到内存中。

我认为没有办法在休眠中使用 Stream 设置或获取数据库字段,并且 HQL 仅插入到 SELECT 语句中。

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

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

所以数据不会完全存储在内存中。

于 2012-08-12T23:12:55.527 回答