NativeQueryじゃだめ?〜JPAクエリ表現ごとのパフォーマンス比較
このエントリはJavaEE Advent Calendar 2012の20日目です。昨日は@den2snさんのJavaを知らない世代が今からはじめるJavaEE開発でした。明日は@javaflavorさんです。
先月、岡山を訪れた時、Javaエバンジェリストである寺田さん(@yoshioterada)に「今仕事でやってるプロジェクトにJPAを導入したんですが、JPQLになじめなくって、素のSQLをNative Queryで投げてるんです。」という話をしたら、「Native Queryはパフォーマンス的に良くなかったはず…」との情報を頂きました。それが本当なら、今のプロジェクトが危ない!正確に言うと、今のプロジェクトにJPAを持ち込んだ自分の身が危ない!
ということで今回は、JPAに用意されている、Native Query、JPQL、criteria API 、それぞれのAPIのパフォーマンスを比較してみることにしました。
まず、それぞれの復習です。
- Native Query
List<Product> products = em.createNativeQuery("select * from product where id = 100").getResultList();
- JPQL
List<Product> products = em.createQuery("select p from product p where p.id = 100").getResultList();
- Criteria API
CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Product> cq = cb.createQuery(Product.class); Root<Product> r = cq.from(Product.class); cq.select(r).where(cb.equal(r.get("id"), "100")); List<Product> products = em.createQuery(cq).getResultList();
- Entity直接指定(おまけ)
- これぐらい簡単なクエリなら、Entityで直接指定して取ってくることも可能です。
Product product = em.find(Product.class, 100);
では、実験です。
今回DBには、軽量JavaDBであるH2を使います。使い方は簡単。zipを落としてきて、h2-xxx.jarにクラスパスを通すだけ。今回は使用したのはバージョン1.3.170です。
続いて、JPAの実装には、RIであるeclipseLinkを使います。使用バージョンは2.4.1。zipをDLして、jlib配下にあるeclipselink.jarと、jlib/jpa配下にあるjavax.persistence_2.0.4.v201112161009.jarにクラスパスを通します。
ソースはこちら。
実験では10万件のデータをDBに投入しておき、ランダムな1件を引いてくることを1000回ずつ、各クエリごとに行います。それを5回実行した平均の結果がこれ。
クエリ | 時間(ミリ秒) |
---|---|
Native Query | 421 |
JPQL | 1232 |
Criteria API | 804 |
Entity | 34 |
NativeQueryを基準とすると、Criteria APIはその2倍、JPQLは3倍の時間がかかりました。Entityでの指定はどれよりもダントツ速い結果になりました。
おそらく、NativeQueryはそのままDBに渡しているだけに対して、JPQLはJPQL文のパース・SQL文の構築・実行、Criteriaはパースが不要でSQLの構築・実行のみ、これがそのまま結果に出ているのだと思われます。Entityが異様に速いのはなぜなんだ…。
(追記)Entityの取得が速いのは、persistした後インスタンスはすぐにGCされるわけではない(いつGCされるかはわからない)のでL1キャッシュに入るので、実際にDBを探索しているのではなく、ヒープに乗っているインスタンスへの参照が返されているだけだからでは、とのご指摘を頂きました。megascusさん、ありがとうございます。
今回は最も単純なクエリでしか実験しなかったため、結果的にはNativeQueryでも問題ない結果になってしまい、寺田さんの真意はわからずじまいでしたが、
- テーブル結合した場合
- Prepared Statementにして、文キャッシュを効かせた場合
- EclipseLinkでなく、Hibernateなど、別の実装の場合
などなど、どんな場面でどんな結果になるのか、機会があればこれらも試してみたい所です。
(余談) 今回DBにH2を使ったのは、直前にjUnit実践入門で読んでいたためです。12章に紹介があって、設定によって、OracleのフリやPostgresのフリをさせられますし、インメモリDBとしても動かせるのでスローテストの解消にも使えそうです。H2を探ってみるのも面白そうです!
【補足】次エントリで追加実験を行い、JPQL遅くない!という結果になったので、合わせてご覧下さい!