Help us understand the problem. What is going on with this article?

Spring Data JPA の Specificationでらくらく動的クエリー

More than 5 years have passed since last update.

Spring DATA JPA では Repository インターフェイスにメソッドを定義するだけで簡単にクエリーを実装することができるが、任意指定の検索条件があるような動的クエリーの実装には向いていない。

動的クエリーとは、検索条件の値が変化するだけでなく、検索条件そのものが変化するようなクエリーである。

例えば、ユーザーの検索条件に「ユーザー名」と「メールアドレス」の2つがある場合、findByNameAndEmail()というメソッドをつくればいいが、検索条件が任意入力の場合、

  • 両方入力 -> findByNameAndEmail()
  • ユーザー名のみ入力 -> findByName()
  • メールアドレスのみ入力 -> findByEmail()
  • 両方未入力 -> findAll()

の4パターンの検索メソッドが必要になってしまう。
4つくらいならそれぞれに対応したfindBy***メソッドを作成してもいいが、検索条件が増えてくるととても対応しきれなくなる。

こういったケースでは、Specification を使用して「パラメータが未入力だったら無効になる検索条件」を実装すると簡単に動的クエリーを作成することができる。

Specificationは何らかの検索条件を表すインターフェイスで、実装クラスではCriteriaAPIを使用して検索条件を実装する。

Specification を使用するには、先ず Repositoryインターフェイスを JpaSpecificationExecutor を継承した形にする。
JpaSpecificationExecutor には List<T> findAll(Specification<T> spec) 等のメソッドがあり、これでSpecificationを使った検索を実行できる。

UserRepositoly.java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
}

次に、検索条件を実装した Specification を返すメソッド群を作成する。
ここで引数がnullや空文字の時にnullを返すことでその検索条件を無効にすることができる。

UserSpecifications.java
import org.springframework.data.jpa.domain.Specification;
public class UserSpecifications {
    /**
     * 指定文字をユーザー名に含むユーザーを検索する。
     */
    public Specification<User> nameContains(String name) {
        return StringUtils.isEmpty(name) ? null : new Specification<User>() {
            @Override
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                return cb.like(root.get("name"), "%" + name + "%");
            }
        };
    }
    /**
     * 指定文字をメールアドレスに含むユーザーを検索する。
     */
    public Specification<User> emailContains(String name) {
        :
    }
}

java8のラムダ式で書くとこんな感じ。

UserSpecifications.java
    public Specification<User> nameContains(String name) {
        return StringUtils.isEmpty(name) ? null : (root, query, cb) -> {
            return cb.like(root.get("name"), "%" + name + "%");
        };
    }

最後に呼び出し側では Specifications を使用して検索条件を結合する。
ここでは引数のnullチェック等は不要なのでそのまま呼び出せばよい。
検索条件が増えた場合には、対応するSpecificationを返すメソッドとそれを呼び出す行を追加するだけで良い。

UserService.java
import static UserSpecifications.*;
public List<User> findUsers(String name, String email, Tag followTag, Long ContributionCount) {
    return userRepository.findAll(Specifications
        .where(nameContains(name))
        .and(emailContains(email))
        .and(flolowTagsHas(followTag))
        .and(contributionCountGreaterThan(Contribution))
    );
}
tag1216
Qiita戦闘力はキュイレベルです! /作ったもの ◆QiiTrend:https://qiitrend.herokuapp.com/ ◆Qiiner:https://qiiner.tag1216.net/ ◆Qiitaでいいねしたら草生えるページ:https://qiiner.tag1216.net/likes-heatmap
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away