Hatena::ブログ(Diary)

Dev3TechHack

2013-01-03

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

16:01

前エントリで、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 Query436421
JPQL701232
Criteria API600804
Entity3434

あらまぁ…


なお、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 Query2734
JPQL1929

おお!やはり、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 Query2335
JPQL1808

NativeQueryで多少の改良が見られました!

ただ、差はそこまで大きくなくなってきたので、導入しやすい方を使用すれば、そこまで問題にはならなそうです。

ご指摘、ありがとうございました!

zyakezyake 2014/04/18 21:34 SELECT 「*」だと、サーバサイドで解析が必要になって速度が落ちるはずですよ。

hogemanhogeman 2015/06/19 16:27 JPAの方はバインド変数使う形になっているのに、
ネイティブクエリーの方は使ってないですね?
バインド変数使ったほうがはやいって
話にすり替わっている気がします。

hogemanhogeman 2015/06/19 16:28 しまった、最後まで読んでなかった。
ゴメンナサイ。