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

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

1. 前書き

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

Criteria API offers a programmatic way to create typed queries。これは構文エラーを回避するのに役立ちます。 さらに、Metamodel APIで使用すると、正しいフィールド名とタイプを使用したかどうかをコンパイル時にチェックします。

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

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

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

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

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

@Entity
class Book {

    @Id
    Long id;
    String title;
    String author;

    // getters and setters

}

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

3. @Repositoryクラス

ご存知のように、Springコンポーネントモデルではwe should place our data access logic in @Repository beansです。 もちろん、このロジックは、Criteria APIなどの任意の実装を使用できます。

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

@Repository
class BookDao {

    EntityManager em;

    // constructor

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

        Root 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 query = em.createQuery(cq);
        return query.getResultList();
    }

}

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

  • まず、CriteriaBuilder参照を取得します。これを使用して、クエリのさまざまな部分を作成できます。

  • CriteriaBuilderを使用して、クエリで実行する内容を説明するCriteriaQuery<Book>を作成します。 また、結果の行の型を宣言します

  • CriteriaQuery<Book>を使用して、クエリの開始点(Bookエンティティ)を宣言し、後で使用できるようにbook変数に格納します。

  • 次に、CriteriaBuilderを使用して、Bookエンティティに対する述語を作成します。 これらの述語はまだ効果がないことに注意してください

  • 両方の述語をCriteriaQuery.に適用します。CriteriaQuery.where(Predicate…)はその引数を論理andに結合します。 これは、これらの述語をクエリに結び付けるポイントです

  • その後、CriteriaQueryからTypedQuery<Book>インスタンスを作成します

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

DAOクラスを@Repositoryでマークしたので、このクラスのSpring enables exception translationに注意してください。

4. カスタムメソッドによるリポジトリの拡張

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

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

さらに、if we want a @Repository interface to have a method with a custom implementation, we can use composable repositories

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

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

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

interface BookRepository extends JpaRepository, BookRepositoryCustom {}

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

@Repository
class BookRepositoryImpl implements BookRepositoryCustom {

    EntityManager em;

    // constructor

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

}

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

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

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

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

    Root book = cq.from(Book.class);
    List 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();
}

ただし、このアプローチはmakes the code hard to maintainであり、特に多くの述語があり、それらをオプションにしたい場合はそうです。

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

5. JPA仕様の使用

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

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

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

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

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

それらを使用するには、org.springframework.data.jpa.repository.JpaSpecificationExecutor<T>を拡張するリポジトリが必要です。

interface BookRepository extends JpaRepository, JpaSpecificationExecutor {}

このインターフェースdeclares handy methods to work with specifications。 たとえば、次のワンライナーを使用して、指定した作成者のすべての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インスタンスで述語を使用して、単純なケースをよりクリーンで冗長性を少なくします

いつものように、例は利用可能なover on GitHubです。