1. 概要

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

このチュートリアルでは、 SpringDataJPAで@Queryアノテーションを使用してJPQLとネイティブSQLクエリの両方を実行する方法を示します。

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

2. クエリを選択

Spring Dataリポジトリメソッドに対して実行するSQLを定義するために、メソッドに@Queryアノテーションを付けることができます—そのvalue属性には、実行するJPQLまたはSQLが含まれます。

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

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

2.1. JPQL

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

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

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

2.2. ネイティブ

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

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

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

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

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

findAll(Sort)や、メソッドシグネチャの解析によって生成されるメソッドなど、すぐに使用できるメソッドの場合、オブジェクトプロパティのみを使用してsortを定義できます。

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

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

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

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

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

3.2. JPQL

クエリ定義にJPQLを使用すると、Spring Dataは問題なく並べ替えを処理できます—必要なのはタイプSortのメソッドパラメーターを追加することだけです。

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

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

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

また、 @Query アノテーションを使用したため、同じ方法を使用して、ユーザーの名前の長さでソートされたリストを取得できます。

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

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

使用する場合:

Sort.by("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. ページ付け

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

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

4.1. JPQL

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

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

PageRequest パラメーターを渡して、データのページを取得できます。

ページ付けはネイティブクエリでもサポートされていますが、少し追加の作業が必要です。

4.2. ネイティブ

追加の属性countQueryを宣言することにより、ネイティブクエリのページネーションを有効にすることができます。

これは、結果全体の行数をカウントするために実行するSQLを定義します。

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

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

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

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

クエリ内にページネーション用のパラメータを追加することで、これを克服できます。

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

In the above example, we add

\n-- #pageable\n

as the placeholder for the pagination parameter. これは、Spring Data JPAに、クエリを解析してページング可能なパラメーターを挿入する方法を指示します。 このソリューションは、H2データベースで機能します。

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

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

メソッドパラメータをクエリに渡すには、インデックス付きパラメータと名前付きパラメータの2つの方法があります。

このセクションでは、インデックス付きパラメータについて説明します。

5.1. JPQL

JPQLのインデックス付きパラメーターの場合、SpringDataはメソッド宣言に表示されるのと同じ順序でメソッドパラメーターをクエリに渡します。

@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. 名前付きパラメーター

名前付きパラメーターを使用して、メソッドパラメーターをクエリに渡すこともできます。リポジトリメソッド宣言内の@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<User> findUserByNameList(@Param("names") Collection<String> names);

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

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

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

@Modifyingアノテーションをリポジトリメソッドに追加することで、@Queryアノテーションを使用してデータベースの状態を変更できます。

8.1. JPQL

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

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

The return value defines how many rows are updated by the execution of the query. インデックス付きパラメーターと名前付きパラメーターの両方を更新クエリ内で使用できます。

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. インサート

INSERTはJPAインターフェースの一部ではないため、挿入操作を実行するには、 @Modifying を適用し、ネイティブクエリを使用する必要があります。

@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. 動的クエリの例

たとえば、実行時に定義されたセット( email1 email2 、…)からLIKEの電子メールを持つすべてのユーザーを選択する必要がある状況を想像してみてください。 、 emailn

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

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

この場合、静的SQLステートメントを提供できないため、@Queryアノテーションを使用することはできません。

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

9.2. カスタムリポジトリとJPACriteriaAPI

幸いなことに、 Springは、カスタムフラグメントインターフェイスを使用してベースリポジトリを拡張する方法を提供します。次に、それらをリンクして複合リポジトリを作成できます。

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

public interface UserRepositoryCustom {
    List<User> findUserByEmails(Set<String> emails);
}

そして、それを実装します。

public class UserRepositoryCustomImpl implements UserRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

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

        Path<String> emailPath = user.get("email");

        List<Predicate> 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 CriteriaAPIを利用して動的クエリを構築しました。

また、クラス名にImpl接尾辞を含める必要があります。SpringはUserRepositoryCustom実装をUserRepositoryCustomImplとして検索します。 フラグメントはそれ自体がリポジトリではないため、Springはこのメカニズムに依存してフラグメントの実装を見つけます。

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

セクション2からセクション7までのすべてのクエリメソッドがUserRepositoryにあることに注意してください。

そこで、 UserRepository の新しいインターフェイスを拡張して、フラグメントを統合します。

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

9.4. リポジトリの使用

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

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

userRepository.findUserByEmails(emails);

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

10. 結論

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

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

いつものように、この記事で使用されている完全なコード例は、GitHubから入手できます。