1. 序章

Spring Data JPAは、クエリメソッドやカスタムJPQLクエリなどのエンティティを処理する多くの方法を提供します。 ただし、 CriteriaAPIQueryDSLなど、よりプログラム的なアプローチが必要になる場合があります。

Criteria APIは、型付きクエリをプログラムで作成する方法を提供します。これは、構文エラーを回避するのに役立ちます。 さらに、Metamodel APIで使用すると、コンパイル時に、正しいフィールド名とタイプが使用されているかどうかがチェックされます。

ただし、欠点があります。ボイラープレートコードで肥大化した冗長なロジックを作成する必要があります。

このチュートリアルでは、基準クエリを使用してカスタムDAOロジックを実装する方法と、Springが定型コードの削減にどのように役立つかを説明します。

2. サンプルアプリケーション

簡単にするために、サンプルでは、同じクエリを複数の方法で実装します。著者の名前とStringを含むタイトルで本を検索します。

このためのBookエンティティは次のようになります。

@Entity
class Book {

    @Id
    Long id;
    String title;
    String author;

    // getters and setters

}

物事をシンプルにしたいので、このチュートリアルではMetamodelAPIを使用しません。

3. @Repositoryクラス

ご存知のとおり、Springコンポーネントモデルでは、データアクセスロジックを@RepositoryBeansに配置する必要があります。 もちろん、このロジックでは、CriteriaAPIなどの任意の実装を使用できます。

これを行うには、 EntityManager インスタンスのみが必要です。これは、自動配線できます。

@Repository
class BookDao {

    EntityManager em;

    // constructor

    List<Book> findBooksByAuthorNameAndTitle(String authorName, String title) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Book> cq = cb.createQuery(Book.class);

        Root<Book> book = cq.from(Book.class);
        Predicate authorNamePredicate = cb.equal(book.get("author"), authorName);
        Predicate titlePredicate = cb.like(book.get("title"), "%" + title + "%");
        cq.where(authorNamePredicate, titlePredicate);

        TypedQuery<Book> query = em.createQuery(cq);
        return query.getResultList();
    }

}

上記のコードは、標準のCriteriaAPIワークフローに従います。

  • まず、 CriteriaBuilder 参照を取得します。これを使用して、クエリのさまざまな部分を作成できます。
  • を使用して CriteriaBuilder 、作成します CriteriaQuery 、クエリで実行したいことを説明します。 また、結果の行のタイプを宣言します
  • CriteriaQuery クエリの開始点を宣言します(エンティティ)、そして私たちはそれをに保存します後で使用するための変数
  • 次に、 CriteriaBuilder を使用して、Bookエンティティに対する述語を作成します。 これらの述語はまだ効果がないことに注意してください
  • 両方の述語をCriteriaQuery。CriteriaQuery.where(Predicate…)に適用し、その引数を論理に結合します。 これは、これらの述語をクエリに結び付けるときのポイントです
  • その後、作成します TypedQuery 私たちからのインスタンス CriteriaQuery
  • 最後に、一致するすべてのBookエンティティを返します

DAOクラスを@Repositoryでマークしたため、Springはこのクラスの例外変換を有効にすることに注意してください。

4. カスタムメソッドを使用したリポジトリの拡張

自動カスタムクエリを持つことは、強力なSpringData機能です。 ただし、自動クエリメソッドでは作成できない、より高度なロジックが必要になる場合があります。

これらのクエリは、(前のセクションのように)個別のDAOクラスに実装できます。

さらに、 @Repositoryインターフェースにカスタム実装のメソッドを持たせたい場合は、コンポーザブルリポジトリを使用できます。

カスタムインターフェイスは次のようになります。

interface BookRepositoryCustom {
    List<Book> findBooksByAuthorNameAndTitle(String authorName, String title);
}

そして、 @Repository インターフェース:

interface BookRepository extends JpaRepository<Book, Long>, BookRepositoryCustom {}

また、以前のDAOクラスを変更して BookRepositoryCustom を実装し、名前をBookRepositoryImplに変更する必要があります。

@Repository
class BookRepositoryImpl implements BookRepositoryCustom {

    EntityManager em;

    // constructor

    @Override
    List<Book> findBooksByAuthorNameAndTitle(String authorName, String title) {
        // implementation
    }

}

BookRepository を依存関係として宣言すると、Springは BookRepositoryImpl を検出し、カスタムメソッドを呼び出すときにそれを使用します。

クエリで使用する述語を選択するとします。 たとえば、著者とタイトルで本を検索したくない場合は、一致する著者のみが必要です。

これを行うには複数の方法があります。たとえば、渡された引数がnullでない場合にのみ述語を適用します。

@Override
List<Book> findBooksByAuthorNameAndTitle(String authorName, String title) {
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Book> cq = cb.createQuery(Book.class);

    Root<Book> book = cq.from(Book.class);
    List<Predicate> predicates = new ArrayList<>();
    
    if (authorName != null) {
        predicates.add(cb.equal(book.get("author"), authorName));
    }
    if (title != null) {
        predicates.add(cb.like(book.get("title"), "%" + title + "%"));
    }
    cq.where(predicates.toArray(new Predicate[0]));

    return em.createQuery(cq).getResultList();
}

ただし、このアプローチでは、特に多くの述語があり、それらをオプションにしたい場合は、コードの保守が困難になります

これらの述語を外部化することは実用的な解決策になるでしょう。 JPA仕様を使用すると、まさにこれを実行できます。 そしてさらにもっと。

5. JPA仕様の使用

Spring Dataは、単一の述語をカプセル化するためにorg.springframework.data.jpa.domain.Specificationインターフェースを導入しました。

interface Specification<T> {
    Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb);
}

仕様インスタンスを作成するメソッドを提供できます。

static Specification<Book> hasAuthor(String author) {
    return (book, cq, cb) -> cb.equal(book.get("author"), author);
}

static Specification<Book> titleContains(String title) {
    return (book, cq, cb) -> cb.like(book.get("title"), "%" + title + "%");
}

それらを使用するには、リポジトリを拡張する必要があります org.springframework.data.jpa.repository.JpaSpecificationExecutor

interface BookRepository extends JpaRepository<Book, Long>, JpaSpecificationExecutor<Book> {}

このインターフェースは、仕様を操作するための便利なメソッドを宣言します。 たとえば、これで、次のワンライナーを使用して、指定された作成者のすべてのBookインスタンスを見つけることができます。

bookRepository.findAll(hasAuthor(author));

残念ながら、複数のSpecification引数を渡すことができるメソッドはありません。 むしろ、org.springframework.data.jpa.domain.Specificationインターフェースでユーティリティメソッドを取得します。

たとえば、2つの Specificationインスタンスを論理および:と組み合わせます。

bookRepository.findAll(where(hasAuthor(author)).and(titleContains(title)));

上記の例では、 where()Specificationクラスの静的メソッドです。

このようにして、クエリをモジュール化できます。 さらに、CriteriaAPIボイラープレートを作成する必要はありませんでした。Springが提供してくれました。

これは、基準の定型文を作成する必要がなくなるという意味ではないことに注意してください。 このアプローチでは、提供された条件を満たすエンティティを選択するという、私たちが見たワークフローのみを処理できます。

クエリには、サポートされていない多くの構造を含めることができます。たとえば、グループ化、選択している別のクラスの返送、サブクエリなどです。

6. 結論

このチュートリアルでは、Springアプリケーションで条件クエリを使用する3つの方法を見ました。

  • DAOクラスの作成は、最も簡単で柔軟な方法です
  • @Repositoryインターフェースを拡張して自動クエリとのシームレスな統合
  • Specification インスタンスで述語を使用して、単純なケースをよりクリーンで冗長性の少ないものにします

いつものように、例はGitHubから入手できます。