【java】【JPA】【eclipseLink】大量データを安全に取得する

IT
スポンサーリンク

こちらや、こちらで記事を書いていた通り、

現在はjbatchな仕事をしていて、DBアクセスにはJPAを採用しています。

JPA自体は、実はあまりbatch向きではないようにも思えますが、

batchなので当然、大量にデータを処理する必要がある場面に出くわします。

 

大量の度合いにもよりますが、

普通にqueryを発行して結果をEntityで受けて・・・

なんてやっていると、

JPAのEntity管理機構により、発行したqueryの結果の

Entityがメモリに積み上がり、すぐにOutOfMemoryなどで

落ちてしまいます。

さて、どうしましょうか??

 

 

通常な使い方だとこんな感じ

    @SuppressWarnings("unchecked")
    public List<TestUserTableEntity> select() {

        Query query = entityManager.createNativeQuery("SELECT * FROM TEST_USER_TABLE", TestUserTableEntity.class);
        return (List<TestUserTableEntity>) query.getResultList();
    }

ここでは、NativeQuery(SQLを直接発行するもの)を使用しています。

jpaなので、jpqlを使用したものや、Entityに名前付きSQLとして発行する

NamedQueryなど色々あるのですが、それはおいおいで。

直感的にわかりやすいNativeQueryとしています。

 

 

これだと何がダメなのか?

query.getResultList()がSQLの結果をEntityの形にマッピングし、

返却してくれます。

当然、Listの1要素が1レコードです。

 

ここで、query.getResultList()の結果が1000万件くらいあったとしたらどうでしょう?

jpaではselectした結果はresultSet→Entityのマッピングを行うため、

単純に考えて2倍のメモリが必要になりますね。

なので、1000万件を格納する容量があっても、

メモリはあっという間にパンクする恐れがあります。

 

 

 

そこで、カーソルの出番です

    public <T> List<T> getCacheSafeEntity(Query query, Class<T> clazz) {

        // この記述にはguavaが必要。
        List<T> list = Lists.newArrayList();

        // カーソル処理設定
        query.setHint(QueryHints.CURSOR, true);

        CursoredStream cursor = (CursoredStream) query.getSingleResult();

        long processedRow = 0L;

        while (cursor.hasNext()) {

            T entity = (T) cursor.next();

            // Select結果をsql以外の条件でフィルターする領域
            if (filterProcess(entity)) {
                list.add(entity);
            }

            processedRow++;

            if (processedRow % batchBlockSize == 0) {
                cursor.clear();
            }
        }

        cursor.close();

        return list;
    }

 

DBでプロシージャなどを実装したことがある方にはおなじみ、

カーソルの出番です。

eclipseLinkではカーソルを使用することができるような

実装がされています。

 

ということで、こんなメソッドを用意しました。

queryは呼び元で生成して、返すEntityの型をコンパイラに判断させるために、

引数としてClass型をもらいます。

 

queryにHistとしてカーソル使用する設定をします。

そうして、query.getSingleResultで取得し、CursorStreamという型で受けます。

カーソル使用を設定しておくと、1件毎のループが回るようになります。

 

ただ、ここで気をつけるのは、

カーソルを使っていてもそのままループさせていれば

通常の使用と同じ問題がおきます。

そこで、

処理件数をインクリメントさせつつ、

ある件数に到達した際に、

カーソルをクリアして上げる必要があります。

 

そうすることで1000万件回し切ることができます。

あ、最後にカーソルのクローズを忘れずに。

 

少し余談

QueryHintsの中に、QueryHints.SCROLLABLE_CURSORというのがあります。

一見すると、なんかカーソルより使えそうな感じがしますね。

ですが、検証してみたところ、通常の場合と同じようにOutOfMemoryで

エラーになりました。

そこで、QueryHints.CACHE_USAGEというものがあり、

それにCacheUsage.NoCahceをセットすれば動作することが確認できました。

何をしているかというと、

select結果をキャッシュしないでねという設定になります。

また、QueryHints.SCROLLABLE_CURSORを使用した場合は、

上記で示したサンプルと動作時間を比較したときに、

数十秒もの差が出て遅かったんです。

ハッキリと確認したわけではないですが、

eclipseLinkの実装を見た感じだと、

sqlの発行回数に差があり、その分が動作時間差にでているように

見えました。

 

 

まとめ

と、いうわけでjpaによる大量データ取得の仕方でした。

カーソル的に処理する機構がeclipseLinkにあるので、

それをうまく使いましょうということでした。

 

insertを大量にする場合も、これと同じように、

ある件数でEntityManagerをflushしてcloseするという流れに

すれば問題なく処理できます。

 

flush:更新されたEntityのsql発行

close:管理対象Entityの開放

 

それでは!

 

ブログランキング・にほんブログ村へ
にほんブログ村


人気ブログランキング

 

コメント

タイトルとURLをコピーしました