Spring Data JPA @Query

Spring Data JPA @Query

1. 概要

Spring Dataは、実行可能なクエリを定義する多くの方法を提供します。 これらの1つは、@Queryアノテーションです。

このチュートリアルでは、how to use the @Query annotation in Spring Data JPA to execute both JPQL and native SQL queriesを示します。

また、@Queryアノテーションが十分でない場合に動的クエリを作成する方法も示します。

参考文献:

Spring Data JPAリポジトリの派生クエリメソッド

Spring Data JPAのクエリ派生メカニズムを調べます。

Spring Data JPA @Modifying Annotation

@Queryアノテーションと@Modifyingアノテーションを組み合わせて、Spring Data JPAでDMLおよびDDLクエリを作成する

2. クエリを選択

Spring Dataリポジトリメソッドに対して実行するSQLを定義するために、annotate the method with the @Query annotation  — its value attribute contains the JPQL or SQL to execute.を実行できます。

@Queryアノテーションは、@NamedQueryアノテーションが付けられているか、orm.xmlファイルで定義されている名前付きクエリよりも優先されます。

名前付きクエリとしてドメインモデル内ではなく、リポジトリ内のメソッドのすぐ上にクエリ定義を配置することをお勧めします。 リポジトリは永続性を担当するため、これらの定義を保存するのに適した場所です。

2.1. JPQL

デフォルトでは、クエリ定義はJPQLを使用します。

データベースからアクティブなUserエンティティを返す単純なリポジトリメソッドを見てみましょう。

@Query("SELECT u FROM User u WHERE u.status = 1")
Collection findAllActiveUsers();

2.2. ネイティブ

ネイティブSQLを使用してクエリを定義することもできます。 nativeQuery属性の値をtrueに設定し、アノテーションのvalue属性でネイティブSQLクエリを定義するだけです。

@Query(
  value = "SELECT * FROM USERS u WHERE u.status = 1",
  nativeQuery = true)
Collection findAllActiveUsersNative();

3. クエリで順序を定義する

タイプSort の追加パラメーターを、@Queryアノテーションを持つSpringDataメソッド宣言に渡すことができます。 これは、データベースに渡されるORDER BY句に変換されます。

3.1. JPAが提供および派生したメソッドの並べ替え

findAll(Sort)のようにすぐに使用できるメソッド、またはメソッドシグネチャwe can only use object properties to define our sortの解析によって生成されたメソッドの場合:

userRepository.findAll(new Sort(Sort.Direction.ASC, "name"));

ここで、nameプロパティの長さで並べ替えたいと想像してください。

userRepository.findAll(new Sort("LENGTH(name)"));

上記のコードを実行すると、例外が発生します。

org.springframework.data.mapping.PropertyReferenceException: No property lENGTH(name) found for type User!

3.2. JPQL

When we use JPQL for a query definition, then Spring Data can handle sorting without any problem —必要なのは、タイプSortのメソッドパラメータを追加することだけです。

@Query(value = "SELECT u FROM User u")
List findAllUsers(Sort sort);

このメソッドを呼び出して、Sortパラメータを渡すことができます。これにより、Userオブジェクトのnameプロパティによって結果が並べ替えられます。

userRepository.findAllUsers(new Sort("name"));

また、@Queryアノテーションを使用したため、we can use the same method to get the sorted list of Users by the length of their names

userRepository.findAllUsers(JpaSort.unsafe("LENGTH(name)"));

JpaSort.unsafe()を使用してSortオブジェクトインスタンスを作成することが重要です。

使用する場合:

new Sort("LENGTH(name)");

次に、findAll()メソッドについて上記で見たのとまったく同じ例外を受け取ります。

Spring Dataは、@Queryアノテーションを使用するメソッドの安全でないSort順序を検出すると、クエリにsort句を追加するだけで、並べ替えるプロパティがドメインモデルに属しているかどうかのチェックをスキップします。 。

3.3. ネイティブ

@QueryアノテーションがネイティブSQLを使用している場合、Sortを定義することはできません。

その場合、例外が発生します。

org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException: Cannot use native queries with dynamic sorting and/or pagination

例外として、この並べ替えはネイティブクエリではサポートされていません。 このエラーメッセージは、ページネーションによって例外が発生することを示唆しています。

ただし、ページ付けを可能にする回避策があり、次のセクションで説明します。

4. ページ付け

ページネーションを使用すると、結果全体のサブセットのみをPageで返すことができます。 これは、たとえば、Webページ上のデータの複数のページをナビゲートするときに便利です。

ページネーションのもう1つの利点は、サーバーからクライアントに送信されるデータの量が最小限に抑えられることです。 より小さなデータを送信することにより、一般的にパフォーマンスが向上します。

4.1. JPQL

JPQLクエリ定義でページネーションを使用するのは簡単です:

@Query(value = "SELECT u FROM User u ORDER BY id")
Page findAllUsersWithPagination(Pageable pageable);

PageRequestパラメータを渡して、データのページを取得できます。 ページネーションはネイティブクエリでもサポートされますが、少し追加の作業が必要です。

4.2. ネイティブ

enable pagination for native queries by declaring an additional attribute countQuery —これは、結果全体の行数をカウントするために実行するSQLを定義します。

@Query(
  value = "SELECT * FROM Users ORDER BY id",
  countQuery = "SELECT count(*) FROM Users",
  nativeQuery = true)
Page findAllUsersWithPagination(Pageable pageable);

4.3. 2.0.4より前のSpringDataJPAバージョン

上記のネイティブクエリのソリューションは、Spring Data JPAバージョン2.0.4以降で正常に機能します。

そのバージョンより前では、このようなクエリを実行しようとすると、例外が発生します。これは、前の並べ替えのセクションで説明したものと同じです。

この問題を解決するには、クエリ内にページネーションのパラメーターを追加します。

@Query(
  value = "SELECT * FROM Users ORDER BY id \n-- #pageable\n",
  countQuery = "SELECT count(*) FROM Users",
  nativeQuery = true)
Page findAllUsersWithPagination(Pageable pageable);

」をページネーションパラメータのプレースホルダーとして使用します。 これは、Spring Data JPAにクエリの解析方法とページング可能なパラメーターの注入方法を指示します。 このソリューションは、H2データベースで機能します。

JPQLとネイティブSQLを使用して単純な選択クエリを作成する方法について説明しました。 次に、追加のパラメータを定義する方法を示します。

5. インデックス付きクエリパラメータ

メソッドパラメータをクエリに渡すには、2つの方法があります。 このセクションでは、インデックス付きパラメータについて説明します。

5.1. JPQL

JPQLのインデックス付きパラメーターの場合、Spring Dataはpass method parameters to the query in the same order they appear in the method declaration:になります

@Query("SELECT u FROM User u WHERE u.status = ?1")
User findUserByStatus(Integer status);

@Query("SELECT u FROM User u WHERE u.status = ?1 and u.name = ?2")
User findUserByStatusAndName(Integer status, String name);

上記のクエリの場合、statusメソッドパラメータはインデックス1,のクエリパラメータに割り当てられ、nameメソッドパラメータはインデックス2のクエリパラメータに割り当てられます。

5.2. ネイティブ

ネイティブクエリのインデックス付きパラメーターは、JPQLとまったく同じように機能します。

@Query(
  value = "SELECT * FROM Users u WHERE u.status = ?1",
  nativeQuery = true)
User findUserByStatusNative(Integer status);

次のセクションでは、別のアプローチ、つまり名前を介してパラメータを渡す方法を示します。

6. 名前付きパラメータ

pass method parameters to the query using named parameters.も可能です。リポジトリメソッド宣言内の@Paramアノテーションを使用してこれらを定義します。

@Paramで注釈が付けられた各パラメータには、対応するJPQLまたはSQLクエリパラメータ名と一致する値文字列が必要です。 名前付きパラメーターを使用したクエリは読みやすく、クエリをリファクタリングする必要がある場合にエラーが発生しにくくなります。

6.1. JPQL

上記のように、メソッド宣言で@Paramアノテーションを使用して、JPQLで名前で定義されたパラメータをメソッド宣言のパラメータと照合します。

@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")
User findUserByStatusAndNameNamedParams(
  @Param("status") Integer status,
  @Param("name") String name);

上記の例では、SQLクエリとメソッドのパラメータを同じ名前で定義しましたが、値の文字列が同じである限り、必須ではありません。

@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")
User findUserByUserStatusAndUserName(@Param("status") Integer userStatus,
  @Param("name") String userName);

6.2. ネイティブ

ネイティブクエリ定義の場合、JPQLと比較して、名前を介してパラメータをクエリに渡す方法に違いはありません。@Paramアノテーションを使用します。

@Query(value = "SELECT * FROM Users u WHERE u.status = :status and u.name = :name",
  nativeQuery = true)
User findUserByStatusAndNameNamedParamsNative(
  @Param("status") Integer status, @Param("name") String name);

7. コレクションパラメータ

JPQLまたはSQLクエリのwhere句にIN(またはNOT IN)キーワードが含まれている場合を考えてみましょう。

SELECT u FROM User u WHERE u.name IN :names

この場合、Collection をパラメータとして受け取るクエリメソッドを定義できます。

@Query(value = "SELECT u FROM User u WHERE u.name IN :names")
List findUserByNameList(@Param("names") Collection names);

パラメータはCollectionであるため、List, HashSetなどで使用できます。

次に、@Modifyingアノテーションを使用してデータを変更する方法を示します。

8. クエリを@Modifyingで更新します

リポジトリメソッドにuse the @Query annotation to modify the state of the database by also adding the @Modifying annotationできます。

8.1. JPQL

データを変更するリポジトリメソッドには、selectクエリと比較して2つの違いがあります。@Modifyingアノテーションがあり、もちろん、JPQLクエリはselectの代わりにupdateを使用します。 s:

@Modifying
@Query("update User u set u.status = :status where u.name = :name")
int updateUserSetStatusForName(@Param("status") Integer status,
  @Param("name") String name);

戻り値は、クエリの実行が更新された行数を定義します。 インデックス付きパラメーターと名前付きパラメーターの両方を更新クエリ内で使用できます。

8.2. ネイティブ

ネイティブクエリを使用してデータベースの状態を変更することもできます。@Modifyingアノテーションを追加するだけです。

@Modifying
@Query(value = "update Users u set u.status = ? where u.name = ?",
  nativeQuery = true)
int updateUserSetStatusForNameNative(Integer status, String name);

8.3. 挿入物

挿入操作を実行するには、@Modifyingを適用し、INSERT is not a part of the JPA interface以降、ネイティブクエリを使用する必要があります。

@Modifying
@Query(
  value =
    "insert into Users (name, age, email, status) values (:name, :age, :email, :status)",
  nativeQuery = true)
void insertUser(@Param("name") String name, @Param("age") Integer age,
  @Param("status") Integer status, @Param("email") String email);

9. 動的クエリ

多くの場合、実行時にのみ値がわかる条件またはデータセットに基づいてSQLステートメントを作成する必要があります。 そして、そのような場合、静的クエリを使用することはできません。

9.1. 動的クエリの例

たとえば、実行時に定義されたセット(email1email2、…、emailn)から、メールがLIKEであるすべてのユーザーを選択する必要がある状況を想像してみましょう。 s:

SELECT u FROM User u WHERE u.email LIKE '%email1%'
    or  u.email LIKE '%email2%'
    ...
    or  u.email LIKE '%emailn%'

セットは動的に構築されるため、コンパイル時に追加するLIKE句の数を知ることはできません。

この場合、we can’t just use the @Query annotation since we can’t provide a static SQL statement.

代わりに、カスタム複合リポジトリを実装することで、基本のJpaRepository機能を拡張し、動的クエリを構築するための独自のロジックを提供できます。 これを行う方法を見てみましょう。

9.2. カスタムリポジトリとJPACriteria API

Luckily for us, Spring provides a way for extending the base repository through the use of custom fragment interfaces.次に、それらをリンクしてcomposite repositoryを作成できます。

まず、カスタムフラグメントインターフェースを作成します。

public interface UserRepositoryCustom {
    List findUserByEmails(Set emails);
}

次に、それを実装します。

public class UserRepositoryCustomImpl implements UserRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public List findUserByEmails(Set emails) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery query = cb.createQuery(User.class);
        Root user = query.from(User.class);

        Path emailPath = user.get("email");

        List predicates = new ArrayList<>();
        for (String email : emails) {
            predicates.add(cb.like(emailPath, email));
        }
        query.select(user)
            .where(cb.or(predicates.toArray(new Predicate[predicates.size()])));

        return entityManager.createQuery(query)
            .getResultList();
    }
}

上に示したように、JPA Criteria APIを利用して動的クエリを作成しました。

Also, we need to make sure to include the Impl postfix in the class name. SpringはUserRepositoryCustom実装をUserRepositoryCustomImplとして検索します。 フラグメント自体はリポジトリではないため、Springはこのメカニズムに基づいてフラグメントの実装を見つけます。

9.3. 既存のリポジトリの拡張

セクション2〜セクション7のすべてのクエリメソッドがUserRepository.にあることに注意してください。次に、UserRepositoryで新しいインターフェースを拡張して、フラグメントを統合します。

public interface UserRepository extends JpaRepository, UserRepositoryCustom {
    //  query methods from section 2 - section 7
}

9.4. リポジトリの使用

最後に、動的クエリメソッドを呼び出すことができます。

Set emails = new HashSet<>();
// filling the set with any number of items

userRepository.findUserByEmails(emails);

複合リポジトリを正常に作成し、カスタムメソッドを呼び出しました。

10. 結論

この記事では、@Queryアノテーションを使用してSpring DataJPAリポジトリメソッドでクエリを定義するいくつかの方法について説明しました。

また、カスタムリポジトリを実装し、動的クエリを作成する方法を学びました。

いつものように、このチュートリアルで使用される完全なコード例は、over on Githubで入手できます。