5

データベースから大量のデータをエクスポートする必要があります。私のデータを表すクラスは次のとおりです。

public class Product{
...

    @OneToMany
    @JoinColumn(name = "product_id")
    @Cascade({SAVE_UPDATE, DELETE_ORPHAN})
    List<ProductHtmlSource> htmlSources = new ArrayList<ProductHtmlSource>();

... }

ProductHtmlSource- 実際にエクスポートする必要がある大きな文字列が含まれています。

エクスポートされたデータのサイズが JVM メモリよりも大きいため、データをチャンク単位で読み取っています。このような:

final int batchSize = 1000;      
for (int i = 0; i < 50; i++) {
  ScrollableResults iterator = getProductIterator(batchSize * i, batchSize * (i + 1));
  while (iterator.getScrollableResults().next()) {
     Product product = (Product) iterator.getScrollableResults().get(0); 
     List<String> htmls = product.getHtmlSources();
     <some processing>
  }

}

のコードgetProductIterator:

public ScrollableResults getProductIterator(int offset, int limit) {
        Session session = getSession(true);
        session.setCacheMode(CacheMode.IGNORE);
        ScrollableResults iterator = session
                .createCriteria(Product.class)
                .add(Restrictions.eq("status", Product.Status.DONE))
                .setFirstResult(offset)
                .setMaxResults(limit)
                .scroll(ScrollMode.FORWARD_ONLY);
        session.flush();
        session.clear();

        return iterator;
    }

問題は、各データチャンクオブジェクトの読み取り後にセッションをクリアしても、Productどこかに蓄積され、OutOfMemory 例外が発生することです。問題は、メモリエラーが発生しなくてもコードのブロックを処理することではありません。1000 個のオブジェクトがメモリに簡単に収まるため、バッチのサイズも問題ではありません。

プロファイラーは、オブジェクトがorg.hibernate.engine.StatefulPersistenceContextクラスに蓄積することを示しました。

スタックトレース:

Caused by: java.lang.OutOfMemoryError: Java heap space
    at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:99)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:518)
    at java.lang.StringBuffer.append(StringBuffer.java:307)
    at org.hibernate.type.TextType.get(TextType.java:41)
    at org.hibernate.type.NullableType.nullSafeGet(NullableType.java:163)
    at org.hibernate.type.NullableType.nullSafeGet(NullableType.java:154)
    at org.hibernate.type.AbstractType.hydrate(AbstractType.java:81)
    at org.hibernate.persister.entity.AbstractEntityPersister.hydrate(AbstractEntityPersister.java:2101)
    at org.hibernate.loader.Loader.loadFromResultSet(Loader.java:1380)
    at org.hibernate.loader.Loader.instanceNotYetLoaded(Loader.java:1308)
    at org.hibernate.loader.Loader.getRow(Loader.java:1206)
    at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:580)
    at org.hibernate.loader.Loader.doQuery(Loader.java:701)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:236)
    at org.hibernate.loader.Loader.loadCollection(Loader.java:1994)
    at org.hibernate.loader.collection.CollectionLoader.initialize(CollectionLoader.java:36)
    at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:565)
    at org.hibernate.event.def.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:63)
    at org.hibernate.impl.SessionImpl.initializeCollection(SessionImpl.java:1716)
    at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:344)
    at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:86)
    at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:109)
    at org.hibernate.collection.PersistentBag.size(PersistentBag.java:225)
    **at com.rivalwatch.plum.model.Product.getHtmlSource(Product.java:76)
    at com.rivalwatch.plum.model.Product.getHtmlSourceText(Product.java:80)
    at com.rivalwatch.plum.readers.AbstractDataReader.getData(AbstractDataReader.java:64)**
4

5 に答える 5

4

getProductIterator() は開始行と行数を期待しているのに対し、開始行番号と終了行番号で getProductIterator() を呼び出しているようです。「上限」が高くなるにつれて、より大きなチャンクでデータを読み取っています。getProductIterator() の 2 番目の引数として batchSize を渡すつもりだと思います。

于 2010-02-11T18:24:19.920 に答える
2

直接的な答えではありませんが、この種のデータ操作には StatelessSession インターフェイスを使用します

于 2010-02-11T18:35:12.240 に答える
2

KeithL の言うとおりです。あなたはますます限界を超えています。しかし、そのように分割することはとにかく意味がありません。スクロール カーソルの要点は、一度に行を処理することであり、チャンクに分割する必要はありません。フェッチ サイズは、より多くのメモリを使用するという犠牲を払って、データベースへのトリップを減らします。一般的なパターンは次のとおりです。

Query q = session.createCriteria(... no offset or limit ...);
q.setCacheMode(CacheMode.IGNORE); // prevent query or second level caching
q.setFetchSize(1000);  // experiment with this to optimize performance vs. memory
ScrollableResults iterator = query.scroll(ScrollMode.FORWARD_ONLY);
while (iterator.next()) {
  Product p = (Product)iterator.get();
  ...
  session.evict(p);  // required to keep objects from accumulating in the session
}

つまり、エラーは getHtmlSources であるため、問題はセッション/カーソル/スクロールの問題とはまったく関係がない可能性があります。これらの html 文字列が巨大で、常に参照されている場合は、連続したメモリが不足している可能性があります。

ところで、ScrollableResults に getScrollableResults メソッドが表示されません。

于 2010-02-11T19:38:12.797 に答える
1

ばかげているように見えるリスクがありますが、これを別の方法で行うことを検討しましたか?

個人的には、データベースから「遠く離れた」バッチ処理を行うことは避けたいと思います。どのデータベースを使用しているかはわかりませんが、途中で適度に単純な操作が必要な場合でも、データベースからデータセットを効率的に取り出してファイルに入れるためのメカニズムが通常あります。ストアド プロシージャ、特定のエクスポート ユーティリティ。データベース ベンダーから他に何が入手できるか調べてください。

于 2010-02-11T08:34:26.110 に答える
0

例外スタックトレースを投稿できますか? GC に適切な JVM オプションを渡すことで解決できる場合があります。

これは関連していると思います - Java StringBuilder の巨大なオーバーヘッド

StackTrace から、非常に大きな文字列が作成され、例外が発生していることがわかります。

于 2010-02-11T07:59:45.890 に答える