読者です 読者をやめる 読者になる 読者になる

Eclipse+JSF+JPAで作るアプリ(18)―JPA Criteria API

今回は、JPAのCriteria APIです。

やりたいことは、前回作った検索画面で、タイトル、カテゴリ、あらすじの3つのフィールドでnullまたは空白だったら無視をして、ANDの検索条件を動的に作ることです。
JPQLのパラメータでは、固定のパラメータとなるため、APIを使って条件に応じたクエリを実装する必要があるはずです。(JPQLの文字列を動的に作成するという方法も、もちろんあります。)

実際の業務アプリケーションでは、

  • 検索条件のオペレーターを「含む」、「=」、「始まりが一致する」「終わりが一致する」など指定できる
  • 空白で区切られた複数のキーワードをORで結合して検索できる
  • 動的に検索するフィールドを追加できる

などの機能が必要になるかもしれませんが、今回の実装では、紹介のみで、そこまでは含みません。

Criteria APIを使ったsearchMovieメソッド

searchMovieの仕様は次の通りです。

  • 画面で入力された、title, category, outlineをand条件にした検索を行っています。
  • 値が空(nullもしくは、isEmptyがtrue)の場合は、条件から外しています。
  • すべて空だった場合はすべて検索します。

先に全体の実装を載せて、各部分ごとに説明していきます。
ちなみに、criteria(クライテリア)はcriterion(クライテリオン)の複数形で、判定基準(の複数形)という意味です。

MovieManager.javaのsearchMovieの実装

public static List<Movie> searchMovie(String title, String category, String outline) {
	EntityManager em = getEm();
	CriteriaBuilder cb = em.getCriteriaBuilder();
	CriteriaQuery<Movie> query = cb.createQuery(Movie.class);
	Root<Movie> root = query.from(Movie.class);
	query.select(root);
	List<Predicate> predicates = new ArrayList<Predicate>();
	if( title != null && !title.isEmpty() )
	{
		predicates.add( cb.like(root.get("title"), "%" + title + "%") );
	}
	if( category != null && !category.isEmpty() )
	{
		predicates.add( cb.equal(root.get("category"), category) );
	}
	if( outline != null && !outline.isEmpty() )
	{
		predicates.add( cb.like(root.get("outline"), "%" + outline + "%") );
	}
	if( predicates.size() > 0 )
	{
		query.where(cb.and(predicates.toArray(new Predicate[]{})));
	}
	query.orderBy(cb.asc(root.get("title")));
	return em.createQuery(query).getResultList();
}

それでは各パーツを見ていきます。

CriteriaBuilder の作成
	CriteriaBuilder cb = em.getCriteriaBuilder();
  • CriteriaBuilderというクエリのビルダをEntityManagerから取得します。
CriteriaQuery の作成
	CriteriaQuery<Movie> query = cb.createQuery(Movie.class);
  • ビルダから検索結果のクラスを指定してクエリクラス CriteriaQuery のインスタンスを作成します。
  • CriteriaQueryには、select, from, where, orderByなどのメソッドがあります。
CriteriaQuery のAPI
	Root<Movie> root = query.from(Movie.class);
	query.select(root);
	query.where(cb.and(predicates.toArray(new Predicate[]{})));
	query.orderBy(cb.asc(root.get("title")));
  • from APIから、検索のルートクラス Rootを作成し、
  • そのRootをselect APIの引数に指定します。
  • where API で、検索条件 Predicate(後述)を指定します。
  • orderBy APIでソート順を指定します。
Predicate 検索条件クラス
	predicates.add( cb.like(root.get("title"), "%" + title + "%") );
	predicates.add( cb.equal(root.get("category"), category) );
  • CriteriaBuilder(ビルダ)クラスから「オペレータAPI名(属性名,条件値)」という形式で検索条件を作成します。
  • 上のwhere句でCreiteriaBuilder#and APIでリストに追加された検索条件をandで結合しています。
  • root.get(フィールド名)の箇所は、Metamodel (クラス名_, Movie_など)を作成することで型安全性を上げることができます。

Metamodelについての説明ははじめての Java Persistence API | 寺田 佳央 - Yoshio Teradaを参照ください。
MetamodelのGeneratorは、java - How to generate JPA 2.0 metamodel? - Stack Overflowを参照ください。
以上が各部分の説明です。

使用感

「何でもできるが取扱いが難しい」というのが使ってみた、解説してみた感想です。

  1. JPQL文字列を動的に作るAPIを作成する。
  2. 簡素化されたラッパーAPIを作成する。

という対処が必要になると思います。

検索画面、機能をリッチにする際に、もう一度取り組んでみたいと思います。