JPAを深掘りする〜Criteria APIで型安全な検索を追求しよう!【応用編】


JPAを深掘りするシリーズの応用編です。

前回はCriteria APIの基本と利点をご紹介しました。今回はCriteria APIの実戦的な機能をいろいろとご紹介します。

準備・エンティティクラスとメタモデルクラスのサンプル

次のようなエンティティクラスとメタモデルクラスが存在することを前提に進めていきます。

まずはUserエンティティクラスです。

次にUserエンティティのプロパティの型を表すメタモデルクラスです。

それではCriteria APIを見ていきましょう。

Where条件にメタモデルクラスを使う

メタモデルクラスは、Criteria APIによるクエリの型安全性を高めるという重要な役割を持っています。

冒頭で示したUserというエンティティクラスをnameプロパティの値で絞り込むクエリを作ってみましょう。

eclipseの入力補完の様子を見ると、fromに指定したエンティティクラスに応じたメタモデルクラスが候補として表示されることが分かると思います。

jps-typesafe-query

 

jps-typesafe-query-2

Where句に複雑な条件を設定する

検索画面のようにWhere句が動的かつ複雑になる時に、Criteria APIは威力を発揮します。例えば入力のある条件はWhere句に加えるが、入力のない条件はWhere句に加えない、という処理をJPQLで書くのは割と大変です。

これをCriteria APIで書き直すと、とても素直なプログラムになります。

 関連クラスを同時に取得する

JPAの強みの1つにエンティティクラスの関連をコードで表現出来ることがあります。下記はUserを親に持つTagというエンティティを作っています。

Tagを取得する際、親のUserも同時に取ってくるようなクエリを作ってみましょう。

9行目がポイントです。実はこの行がなくても関連エンティティは取得できるのですが、SQLの実行回数が格段に変わってきます。俗に言う「N+1問題」を回避する手法です。

関連先のエンティティの条件を指定する

JPAの強力な機能の1つに、関連先エンティティの条件指定がとても簡単な点があります。

6行目がポイントです。「Tagクラス#userプロパティ」→「Userクラス#nameプロパティ」とたどって条件指定しています。SQLで同じことをしようとするとJOINを書く必要があり手間がかかるところですが、JPAではかなりシンプルに書けることが分かります。

Where句に IN条件を指定する

IN条件は少し特殊な書き方をするのでハマることがあります。

7行目でIN条件を指定しています。CriteriaBuilderを使わない点に注意してください。

また要素数が0の時の挙動は、残念ながらDB製品依存となるようなので、プログラムで明示的にチェックするのが無難でしょう。

Tupleを使って柔軟な結果を得る

Tupleという汎用クラスを使えばエンティティにとらわれない形でデータを取得できます。例えば、JOINを使って複数のテーブルからデータを取得しつつ集計するような用途に有用です。

ただし、型安全性が失われる点には注意が必要です。

任意のデータクラスをクエリの結果の型にする

Tupleの弱点は、Tupleが汎用的すぎて「どんなデータがいくつ入っているか分かりにくい」ことです。これを補うために、任意の型をクエリの結果とすることができます。

Tupleの例を書き直してみます。まずは結果を受け取るためのクラスを作ります。コンストラクタで全ての値を受け取るようにする必要があります。

作ったクラスをクエリの結果とするには、CriteriaBuilder#construct(Class, Selection<?>...)を使ってクエリを組み立てます。

9〜11行目でデータクラスを扱っています。

Tupleに比べ扱いやすい型で結果を得られますが、一方でコンストラクタ引数の順番に依存するプログラムとなってしまうのが弱点です。ですがTupleの「どんなデータがいくつ入っているか分かりにくい」という欠点はとても重大ですので、できれば、こちらのデータクラスを使う方が望ましいです。

型安全になりきれていない箇所

ここまで見てきたように、Criteria APIは型安全にクエリを組み立てられる非常に便利な機能なのですが、残念ながら型安全になりきれていない箇所があります。代表的なのはCriteriaBuilder#equal(Expression<?> exp, Object value)メソッドです。

例えば次のコードを見てください。

これを次のように書き間違えたとしても、コンパイルエラーにはならないのです。

このケースでは実行時例外がスローされますが、場合によっては例外がスローされず普通に実行されてしまうため、分かりにくいバグの原因となる可能性があります。

このメソッドのシグニチャが

となっていればよかったのに・・・と思えてなりません。頻繁に使うメソッドだけに残念です。

JPAのその他の機能

最後に、ちょっとした便利機能をいくつか紹介しておきます。

ページングに使える2つのメソッド

Webアプリケーションでは検索結果が大量になる場合、ページング表示するのが普通です。JPAには簡単にページングを実装するためのメソッドがあります。

setFirstResult()/setMaxResults()です。

7行目と8行目でページングを指定しています。

結果が必ず1件と分かっている場合に使えるメソッド

結果が必ず1件と分かっている時にはgetSingleResult()メソッドが使えます。

ただし、結果が1件でなかった時は例外がスローされますので、絶対に1件と確信が持てる場合にのみ使ってください。

 まとめ

Criteria APIを中心に、2回に渡ってJPAの様々な機能を解説してきましたがいかがでしたでしょうか。

JPAには他にも便利で重要な機能がたくさんあります。特にエンティティクラスの設計は工夫のしがいがあり、別の機会でご紹介したいところです。

非常にパワフルなJPA。JavaEEを使う場合はぜひJPAを使って効率的かつ安全にDBアクセスを実装していただきたいと思います。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">