Spring Data JPA @Query

データ]

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

1概要

Spring Dataは実行可能なクエリを定義するための多くの方法を提供します。

その1つが @ Query アノテーションです。

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

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クエリでの順序の定義

@ Query アノテーションを持つSpring Dataメソッドの宣言に __Sort 型の追加パラメータを渡すことができます。データベースに渡される ORDER BY__句に変換されます。

3.1. JPA提供および派生メソッドのソート

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

userRepository.findAll(new Sort(Sort.Direction.ASC, "name"));
  • nameプロパティの長さでソートしたいとしましょう。**

userRepository.findAll(new Sort("LENGTH(name)"));
  • 上記のコードを実行すると、例外が発生します。**

__ org.springframework.data.mapping.PropertyReferenceException:User型のプロパティlENGTH(name)が見つかりません。

==== 3.2. JPQL

  • クエリ定義にJPQLを使用すると、Spring Dataはソートを問題なく処理できます** - Sort 型のメソッドパラメータを追加するだけです。

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

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

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

また、 @ Query アノテーションを使用したので、 同じメソッドを使用して、名前の長さで Users のソート済みリストを取得できます

userRepository.findAllUsers(JpaSort.unsafe("LENGTH(name)"));
  • Sort オブジェクトインスタンスを作成するには、 JpaSort.unsafe() を使用することが重要です。**

使うとき:

new Sort("LENGTH(name)");

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

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

==== 3.3. ネイティブ

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

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

org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException

動的な並べ替えやページ付けを伴うネイティブクエリは使用できません。

例外が言うように、ソートはネイティブクエリではサポートされていません。エラーメッセージは、ページ付けが例外を引き起こすというヒントを私たちに与えます。

ただし、ページネーションを有効にするための回避策があります。次のセクションで説明します。

=== 4ページ付け

ページ付けを使用すると、結果全体のサブセットのみを Page に返すことができます。これは、たとえば、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. Spring Data JPAのバージョン2.0.4より前

ネイティブクエリに対する上記の解決策は、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<User> findAllUsersWithPagination(Pageable pageable);

上記の例では、ページ区切りパラメータのプレースホルダとして「\ n - #pageable \ n」を追加します。これはSpring Data JPAにクエリの解析方法とページング可能パラメータの挿入方法を指示します。この解決策は H2 データベースに有効です。

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

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

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

==== 5.1. JPQL

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

@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

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

@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);

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

=== 7. @ Modifying を使用してクエリを更新する

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

==== 7.1. JPQL

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

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

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

==== 7.2. ネイティブ

データベースの状態はネイティブのクエリでも変更できます - 単に @ Modifying アノテーションを追加する必要があります。

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

=== 8結論

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

いつものように、このチュートリアルで使用されている完全なコード例はhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/spring-data-jpa[Githubで入手可能]です。