Spring Dataアプリケーションで基準クエリを使用する

データ]

  • リンク:/tag/jpa/[JPA]

1.はじめに

Spring Data JPAは、https://www.baeldung.com/the-persistence-layer-with-spring-data-jpa[query methods]やカスタムhttps://www.baeldung.com/など、エンティティを処理するためのさまざまな方法を提供しています。 spring-data-jpa-query[JPQLクエリ]。ただし、もっとプログラム的なアプローチが必要な場合があります。例えば、https://www.baeldung.com/hibernate-criteria-queries[Criteria API]またはhttps://www.baeldung.com/querydsl-with-jpa-tutorial[QueryDSL]。

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

しかし、それには欠点があります。ボイラープレートコードで肥大化した冗長なロジックを記述する必要があります。

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

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

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

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

@Entity
class Book {

    @Id
    Long id;
    String title;
    String author;

   //getters and setters

}

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

3. @ Repository クラス

私たちが知っているように、Springコンポーネントモデル では、データアクセスロジックを @ Repository beans に配置するべきです。もちろん、このロジックはCriteria APIなど、任意の実装を使用できます。

これを行うには、 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();
    }

}

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

  • まず、 CriteriaBuilder 参照を取得します。

クエリのさまざまな部分を作成する ** CriteriaBuilder を使用して、 CriteriaQuery <Book> を作成します。

クエリで何をしたいのかを説明します。また、以下の型を宣言しています。 結果の行 ** CriteriaQuery <Book> では、クエリの開始点を宣言します。

Book entity)、および後で使用するために book 変数に格納します。 ** 次に、 CriteriaBuilder を使用して、 Book に対する述語を作成します。

エンティティ。これらの述語はまだ効果がありません。 ** 両方の述語を__CriteriaQueryに適用します。

CriteriaQuery.where(Predicate …​) は、引数を論理値にまとめます。 そして 。これが、これらの述語をクエリに結び付けるときのポイントです。 ** その後、私たちは TypedQuery <Book> インスタンスを作成します。

CriteriaQuery ** 最後に、一致するすべての Book エンティティを返します。

DAOクラスに @ Repository というマークを付けたので、 Springはこのクラスの例外変換 を有効にします。

4.カスタムメソッドでリポジトリを拡張する

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

これらのクエリを別々のDAOクラスに実装することができます(前のセクションと同じ)。

さらに、 @ Repository インターフェースにカスタム実装のメソッドを持たせたい場合は、https://www.baeldung.com/spring-data-composable-repositories[composable repositories]** を使用できます。

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

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 <T> を拡張するためのリポジトリが必要です。

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

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

bookRepository.findAll(hasAuthor(author));

残念なことに、私たちには複数の Specification 引数を渡すことができるメソッドがありません。そうではなくて、 org.springframework.data.jpa.domain.Specification インターフェースにユーティリティメソッドがあります。

たとえば、2つの Specification インスタンスをlogicalおよび以下のものと組み合わせると

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

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

  • このようにして、クエリをモジュール式にすることができます。その上、Criteria APIの定型文を書く必要はありませんでした。

基準の定型句をもう書く必要がないという意味ではありません。このアプローチは私達が見たワークフローを扱うことができるだけです:

与えられた条件を満たすエンティティを選択する。

  • クエリは、グループ化、選択している別のクラスを返す、サブクエリなど、サポートできない多くの構造を持つことができます。**

6.まとめ

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

  • DAOクラスを作成することは最も直接的で最も柔軟な方法です

  • とのシームレスな統合への @ Repository インターフェースの拡張

自動クエリ ** 単純なケースを作るために Specification インスタンスで述語を使う

よりクリーンで冗長性が低い

いつものように、例はhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/spring-jpa[GitHubで動く]で利用可能です。