Java Persistence API(JPA)で大量データを取得する
ベストっぽい方法を探す
実行環境・ライブラリ
・JavaEE6(JDK7)
・GlashFish v3
模索
メモリを使いすぎる要因
- JPAの、#setFirstResult()、#setMaxResults() は、指定した位置(オフセット)からの指定した件数は返却してくれるのですが、内部的な動きがよくなく、単にフェッチしたものを読み飛ばしているだけでした。
- フェッチだけならまだしも、読み飛ばしているだけで、メモリを使ってしまうのです。
- レコード数が十万件程度で、-Xmx 512MでOutOfMemmory Error が発生
- レコード数が20万件程度で30分かかって、-Xmx 768MでOutOfMemmory Error が発生
JPAでの動き
- 単にフェッチして読み飛ばしている気はしていたが、それがメモリを使いすぎていると気づくまでに時間がかかった。
- JPAでのSQLのログが、件数指定する箇所がなくWHERE句とORDER BY のSQLになっていて、そのまま実行されていることは気づいていたが・・・。
- 今回やろうとしたことは、テーブルのデータをCSVに出力するのですが、1000件フェッチしてCSV出力して、またフェッチして・・・を繰り返していたのですが
Query #setFirstResult(0)、 #setMaxResults(1000) を実行し 次に Query #setFirstResult(1000)、#setMaxResults(2000)
と動く場合に、
1-1000件、1001件-2000件という取得でなく 1-1000件、1-2000件がJPAでは取得して、JPAが1001件から2000件を切り取って返す
といった動きをするのです。
メモリを使いすぎるだけでなく都度1件目からの処理なので、激遅です。
対処・対応
どう対応したかは、今回はOarcleであったためOracleの関数 row_number を使いました。
<named-native-query name="Xxx.findRowNumber" result-set-mapping="XxxEntity"> <query><![CDATA[ Select * from (select row_number() over(order by col1) rn, col2,col3 From TableName Where xxx =?1 Order by col1 ASC) Where rn >= ?2 and rn <= ?3 ]]></query> </named-native-query>
として、?1が検索条件で、?2,?3がoffset,Limitです。
@PersistenceContext private EntityManager em ; ・・・ em.createNamedQuery("Xxx.findRowNumber", XxxEntity.class) .setParameter(1, foo).setParameter(2,offset).setParameter(3,offset + max -1).getResultList();
みたいに、?1が検索条件で、?2,?3がoffset,Limitです。
これで、20万件のCSV出力が1分程度になった。