JPQL速かった!〜JPAクエリ表現ごとのパフォーマンス比較 その2

前エントリで、NativeQuery・JPQL・CriteriaAPIのどれが一番速いのかパフォーマンス比較を行い、NativeQuery悪くないじゃん、と結論づけました。そして、JavaEE AdventCalendar 2013を取りまとめて頂いた@megascusさんや、このネタを書く発端となった@yoshioteradaさんを始めとする多くの方々にコメントを頂きました。ありがとうございました。


その中で、JPQLをNamedQueryで宣言しておくと、起動時にプリコンパイルされるのでパフォーマンスが向上する、との情報を頂いたので、再実験してみました。


今回使用したソースはこちら


前回との差分を簡単に振り返っておくと、まず、JPQL文をfindProductという名前をつけて、EntityクラスにNamedQueryとして宣言しました。

@Entity
@Table(name="product")
@NamedQuery(name="findProduct", query="select p from Product p where p.id = :id")
public class Product {

次に、JPQLの実行部分を、このNamedQueryを呼び出すように変更しました。

// List<Product> productsJPQL = em.createQuery("select p from Product p where p.id = " + r.nextInt(100000)).getResultList();

Query query = em.createNamedQuery("findProduct");
query.setParameter("id", r.nextInt(100000));
List<Product> productsJPQL = (List<Product>)query.getResultList();


そして、気になる実験結果は下記の通り。前回と同様5回実行した平均です。

クエリ NamedQuery(ミリ秒) 非NamedQuery(前回の記録)
Native Query 436 421
JPQL 70 1232
Criteria API 600 804
Entity 34 34

あらまぁ…


なお、Persistence.xmlに下記オプションを追加して、実行されたSQLをログに出力してみました。

<property name="eclipselink.logging.level.sql" value="FINE"/>

すると、EntityとJPQLではSQLは発行されていないようでログにはなにも出ず、L1キャッシュに乗っているインスタンスを取ってきているようでした。また、NativeQueryとCriteriaAPIでは下記の通りにログが出ており、SQLが発行されていることが確認できます。

// NativeQuery
[EL Fine]: sql: 2013-01-03 15:13:31.449--ServerSession(570159399)--Connection(296362180)--select * from product where id = 36569

// Criteria API
[EL Fine]: sql: 2013-01-03 15:13:31.496--ServerSession(570159399)--Connection(296362180)--SELECT ID, NAME FROM product WHERE (ID = ?)
	bind => [1 parameter bound]


というわけで、JPQL速いです!JPQLを使いましょー! …と言いたいところなのですが、ここでふと思うわけです。"DBから取ってくるのと、L1キャッシュから取ってきてる結果を比較して、クエリのパフォーマンス比較になってるのか?”と。


そこで、キャッシュから引いてこれないように、クエリを次のように変えてみました。

@NamedQuery(name="findProducts", query="select p from Product p where p.id > :lowerId and p.id < :upperId")

範囲指定すれば、キャッシュからインスタンスを指定して取ってこれはしないだろう、という目論みです。SQLログを確認すると、確かにこれならJPQLでもDBに対してクエリが発行されていました。

ソースはこちら。
そして、気になる結果は…

クエリ 時間(ミリ秒)
Native Query 2734
JPQL 1929

おお!やはり、JPQL速かった!!


まとめです。
JPAでは、JPQLをNamedQueryにして、起動時にプリコンパイルさせておくのが、速いです。
JPQLにしておくと、JPAの良いところである、永続化装置が何であるかを気にせずに良いという所を活かせるので、一石二鳥ですね。プロジェクトが許すのであれば、JPQLの導入を検討しましょう!


(1/14追記)
さらにご指摘を頂き、

  • NativeQueryもNamedNativeQueryに
  • NativeQueryにバインド変数を使用する

の2点を、考慮して実施してみました。

@NamedNativeQuery(name="findProductsByNative", query="select * from product where id > ?lowerId and id < ?upperId")

ソースはこちら


結果は…

クエリ 時間(ミリ秒)
Native Query 2335
JPQL 1808


NativeQueryで多少の改良が見られました!
ただ、差はそこまで大きくなくなってきたので、導入しやすい方を使用すれば、そこまで問題にはならなそうです。
ご指摘、ありがとうございました!