12

データベースとの間でバイナリ データをストリーミングする方法を探しています。可能であれば、(データベースにとらわれない方法で) Hibernate で実行したいと思います。私が見つけたすべての解決策には、バイナリ データをバイト [] としてメモリに明示的または暗黙的にロードすることが含まれます。私はそれを避ける必要があります。コードでデータベースから 2GB のビデオ (BLOB 列に格納) をローカル ファイルに書き込めるようにしたい、またはその逆で、256Mb 以下のメモリを使用できるようにしたいとします。それは明らかに達成可能であり、ブードゥー教は含まれていません。しかし、方法が見つかりません。今のところ、Hibernate のデバッグを回避しようとしています。

サンプル コードを見てみましょう (-Jmx=256Mb に注意してください)。

エンティティ クラス:

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 を使用してデータベースに BLOB を格納するときにストリームをメモリにロードしないようにする方法です。動画をコンテンツ リポジトリに保存してリンクするのではなく、データベースの列に保存する理由についての話題を避けたいと思います。質問に関係のない機種とお考えください。

基礎となるデータベースがストリーミングブロブなどをサポートしていないため、さまざまな方言に何らかの機能があり、Hibernate がすべてをメモリにロードしようとする可能性があるようです。その場合は、ブロブの処理の観点から、異なる方言間のある種の比較表を見たいと思います。

ご助力ありがとうございます!

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

BlobPOJO に保存していますSimpleBean。これは、ブロブがヒープ領域よりも大きい場合、このオブジェクトを操作したりdataフィールドにアクセスしたりするたびOutOfMemoryErrorに、すべてがメモリに読み込まれるため、 を取得することを意味します。

Hibernate で Stream を使用してデータベース フィールドを設定または取得する方法はないと思います。HQL は SELECT ステートメントにのみ挿入します。

ロードまたは保存時にフィールドがメモリに格納されないようにdata、オブジェクトからフィールドを削除する必要がある場合があります。SimpleBeanただし、ブロブを保存する必要がある場合は、hibernate を使用save()して行を作成してから、jdbc PreparedStatementsetBinaryStream()メソッドを使用できます。ストリームにアクセスする必要がある場合は、hibernate のload()メソッドを使用してSimpleBeanオブジェクトを取得し、jdbc select を実行して取得しResultSet、メソッドを使用しgetBinaryStream()て blob を読み取ることができます。言うためのドキュメントsetBinaryStream()

データは、ファイルの終わりに達するまで、必要に応じてストリームから読み取られます。

そのため、データが完全にメモリに保存されるわけではありません。

于 2012-08-12T23:12:55.527 に答える