JPAでカーソル操作
JPAで複数件取得する時は、getResultListで取得するのが一般的なやり方です。
画面で数十件ずつページングをしながら表示をさせるようなケースではこれでよいのだと思いますが、数万件とかになるとそれが全部オブジェクトとしてヒープに乗ってきてしまうので、OOME起こさないようにとか色々と気になるところがある。
もちろん数万件を全部画面に出すことなんてなく、数万件取ったのを順番に足していってとかはSQLちゃんと書きなさいよという話なのであんまり出番はないのだけれど、「バッチ処理で使うんです」みたいな話であればもしかしたら出番はあるのかも。
JPAでバッチ処理がいいかどうかは置いといて。
やり方
JDBC APIであれば普通にResultSetを取ってクルクル回せばいいだけれなのだけれど、標準のJPA APIにはそんなのはないみたい。やろうと思ったらProvider固有のAPIを使うことになるようです。
WildflyなのでHibernateを使ってやってみることにします。
ScrollableResults
HibernateにはScrollableResultsというそのものズバリっぽい名前のインタフェースがあり、これを使えば万事解決です。
import java.util.HashMap; import java.util.Map; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.hibernate.Query; import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; public class EmpResource { @PersistenceContext private EntityManager em; public Map<Long, String> findAllAsMap() { Map<Long, String> result = new HashMap<>(); ScrollableResults sr = em.createNamedQuery("Emp.all", Emp.class) .unwrap(Query.class).scroll(ScrollMode.FORWARD_ONLY); while (sr.next()) { Emp emp = (Emp) sr.get(0); result.put(emp.getId(), emp.getName()); } em.clear(); return result; } }
標準APIにはないので、Hibernate固有のQueryインタフェースにunwrapしてやると、scrollメソッドが使えます。結果がScrollableResultsなので、あとはnext()でカーソルを進めながら処理をしてやればOKです。
1次キャッシュのクリア
ちゃんと調べたわけではないけれど、EntityManagerで取得したEntityはキャッシュされるようで、カーソルで辿ったとしても数万件回す間にキャッシュにはやっぱりオブジェクトが溜まってしまってOOMEみたいなことになりそうです。
ある程度のタイミングでclear()してやる必要があるようです。
(上のコードは全部終わったあとにやってるのであんまり意味なし)