JPA クエリメソッド
このセクションでは、Spring Data JPA を使用してクエリを作成するさまざまな方法について説明します。
クエリ検索戦略
JPA モジュールは、クエリを文字列として手動で定義すること、またはメソッド名から派生させることをサポートしています。
述語 IsStartingWith
、StartingWith
、StartsWith
、IsEndingWith
、EndingWith
、EndsWith
、IsNotContaining
、NotContaining
、NotContains
、IsContaining
、Containing
、Contains
を持つ派生クエリは、これらのクエリのそれぞれの引数がサニタイズされます。つまり、LIKE
によってワイルドカードとして認識される文字が実際に引数に含まれる場合、これらはエスケープされるため、リテラルとしてのみ一致します。使用されるエスケープ文字は、@EnableJpaRepositories
アノテーションの escapeCharacter
を設定することにより構成できます。値式の使用と比較してください。
宣言されたクエリ
メソッド名から派生したクエリを取得することは非常に便利ですが、メソッド名パーサーが使用したいキーワードをサポートしていないか、メソッド名が不必要にくなる状況に直面するかもしれません。そのため、命名規則を使用して JPA 名前付きクエリを使用するか(詳細については JPA 名前付きクエリの使用を参照)、@Query
を使用してクエリメソッドにアノテーションを付けることができます(詳細については @Query
を使用するを参照)。
クエリ作成
一般に、JPA のクエリ作成メカニズムはクエリメソッドに従って機能します。次の例は、JPA クエリメソッドがどのように変換されるかを示しています。
public interface UserRepository extends Repository<User, Long> {
List<User> findByEmailAddressAndLastname(String emailAddress, String lastname);
}
ここから JPA 条件 API を使用してクエリを作成しますが、本質的にこれはクエリ select u from User u where u.emailAddress = ?1 and u.lastname = ?2
に変換されます。Spring Data JPA は、プロパティ式に従って、プロパティチェックを実行し、ネストされたプロパティを走査します。
次の表は、JPA でサポートされているキーワードと、そのキーワードを含むメソッドが何に変換されるかを示しています。
キーワード | サンプル | JPQL スニペット |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
In および NotIn は、配列または可変引数だけでなく、Collection のサブクラスもパラメーターとして受け取ります。同じ論理演算子の他の構文バージョンについては、リポジトリクエリキーワードを確認してください。 |
ただし、後者のクエリでは、フォーカスが
このクエリのポイントは何ですか? 与えられた名前を持つ人の数を見つけるには? その拘束力のある姓を持つ明確な人々の数を見つけるには? 個別の名前の数を見つけるには? (最後のクエリはまったく異なるクエリです! ) |
JPA 名前付きクエリの使用
例では、<named-query /> 要素と @NamedQuery アノテーションを使用しています。これらの構成要素のクエリは、JPA クエリ言語で定義する必要があります。もちろん、<named-native-query /> または @NamedNativeQuery も使用できます。これらの要素を使用すると、データベースプラットフォームの独立性が失われるため、ネイティブ SQL でクエリを定義できます。 |
XML 名前付きクエリ定義
XML 構成を使用するには、必要な <named-query />
要素を、クラスパスの META-INF
フォルダーにある orm.xml
JPA 構成ファイルに追加します。名前付きクエリの自動呼び出しは、定義済みの命名規則を使用することで有効になります。詳細については、以下を参照してください。
<named-query name="User.findByLastname">
<query>select u from User u where u.lastname = ?1</query>
</named-query>
クエリには、実行時に解決するために使用される特別な名前があります。
インターフェースの宣言
これらの名前付きクエリを許可するには、UserRepository
を次のように指定します。
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByLastname(String lastname);
User findByEmailAddress(String emailAddress);
}
Spring Data は、これらのメソッドへの呼び出しを名前付きクエリに解決しようとします。構成されたドメインクラスの単純な名前で始まり、その後にドットで区切られたメソッド名が続きます。前述の例では、メソッド名からクエリを作成する代わりに、前に定義した名前付きクエリを使用します。
@Query
を使用する
名前付きクエリを使用してエンティティのクエリを宣言することは有効なアプローチであり、少数のクエリに対しては正常に機能します。クエリ自体はそれらを実行する Java メソッドに関連付けられているため、ドメインクラスにアノテーションを付けるのではなく、Spring Data JPA @Query
アノテーションを使用して実際に直接バインドできます。これにより、ドメインクラスが永続性固有の情報から解放され、クエリがリポジトリインターフェースに配置されます。
クエリメソッドにアノテーションが付けられたクエリは、@NamedQuery
を使用して定義されたクエリまたは orm.xml
で宣言された名前付きクエリよりも優先されます。
次の例は、@Query
アノテーションを使用して作成されたクエリを示しています。
@Query
を使用して、クエリメソッドでクエリを宣言する public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
}
QueryRewriter の適用
適用しようとする機能の数に関係なく、EntityManager
に送信される前に、Spring Data JPA に必要なすべてのものをクエリに適用させることが不可能な場合があります。
クエリは EntityManager
に送信される直前に取得し、「書き換え」ることができます。つまり、最後の瞬間にあらゆる変更を加えることができます。クエリの書き換えは、実際のクエリと、該当する場合はカウントクエリに適用されます。カウントクエリは最適化されているため、不要になるか、Hibernate SelectionQuery
から派生するなど、他の方法でカウントが取得されます。
@Query
を使用して QueryRewriter を宣言する public interface MyRepository extends JpaRepository<User, Long> {
@NativeQuery(value = "select original_user_alias.* from SD_USER original_user_alias",
queryRewriter = MyQueryRewriter.class)
List<User> findByNativeQuery(String param);
@Query(value = "select original_user_alias from User original_user_alias",
queryRewriter = MyQueryRewriter.class)
List<User> findByNonNativeQuery(String param);
}
この例は、ネイティブ(純粋な SQL)リライターと JPQL クエリの両方を示しており、どちらも同じ QueryRewriter
を利用しています。このシナリオでは、Spring Data JPA は、対応するタイプのアプリケーションコンテキストに登録されている Bean を探します。
次のようなクエリリライタを記述できます。
QueryRewriter
public class MyQueryRewriter implements QueryRewriter {
@Override
public String rewrite(String query, Sort sort) {
return query.replaceAll("original_user_alias", "rewritten_user_alias");
}
}
Spring Framework の @Component
ベースのアノテーションのいずれかを適用するか、@Configuration
クラス内の @Bean
メソッドの一部として使用するかにかかわらず、QueryRewriter
がアプリケーションコンテキストに登録されていることを確認する必要があります。
もう 1 つのオプションは、リポジトリ自体にインターフェースを実装させることです。
QueryRewriter
を提供するリポジトリ public interface MyRepository extends JpaRepository<User, Long>, QueryRewriter {
@Query(value = "select original_user_alias.* from SD_USER original_user_alias",
nativeQuery = true,
queryRewriter = MyRepository.class)
List<User> findByNativeQuery(String param);
@Query(value = "select original_user_alias from User original_user_alias",
queryRewriter = MyRepository.class)
List<User> findByNonNativeQuery(String param);
@Override
default String rewrite(String query, Sort sort) {
return query.replaceAll("original_user_alias", "rewritten_user_alias");
}
}
QueryRewriter
で何をしているのかによっては、それぞれがアプリケーションコンテキストに登録されている複数のサイトを用意することをお勧めします。
CDI ベースの環境では、Spring Data JPA は BeanManager を検索して、QueryRewriter の実装のインスタンスを探します。 |
高度な LIKE
式の使用
次の例に示すように、@Query
で作成された手動で定義されたクエリのクエリ実行メカニズムにより、クエリ定義内で高度な LIKE
式を定義できます。
like
式 public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.firstname like %?1")
List<User> findByFirstnameEndsWith(String firstname);
}
前の例では、LIKE
区切り文字(%
)が認識され、クエリが有効な JPQL クエリに変換されます(%
が削除されます)。クエリを実行すると、メソッド呼び出しに渡されたパラメーターは、以前に認識された LIKE
パターンで拡張されます。
ネイティブクエリ
@NativeQuery
アノテーションを使用すると、次の例に示すようにネイティブクエリを実行できます。
public interface UserRepository extends JpaRepository<User, Long> {
@NativeQuery(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1")
User findByEmailAddress(String emailAddress);
}
@NativeQuery アノテーションは主に @Query(nativeQuery=true) 用に合成されたアノテーションですが、JPA の @SqlResultSetMapping(…) を活用するために sqlResultSetMapping などの追加属性も提供します。 |
Spring Data は、ページ区切りと並べ替えの単純なクエリを書き換えることができます。より複雑なクエリでは、クラスパスに JSqlParser [GitHub] (英語) を配置するか、コードで countQuery を宣言する必要があります。詳細については、以下の例を参照してください。 |
@NativeQuery
を使用して、クエリメソッドでページネーションのネイティブカウントクエリを宣言します。public interface UserRepository extends JpaRepository<User, Long> {
@NativeQuery(value = "SELECT * FROM USERS WHERE LASTNAME = ?1",
countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1")
Page<User> findByLastname(String lastname, Pageable pageable);
}
有効な値は次のとおりです (大文字と小文字は区別されません)。
|
同様のアプローチは、クエリのコピーに .count
サフィックスを追加することにより、名前付きネイティブクエリでも機能します。ただし、カウントクエリの結果セットマッピングを登録する必要があります。
マップされた結果を取得するだけでなく、ネイティブクエリでは、メソッドの戻り値の型として Map
コンテナーを選択することで、データベースから生の Tuple
を読み取ることができます。結果のマップには、実際のデータベース列名と値を表すキー / 値のペアが含まれます。
interface UserRepository extends JpaRepository<User, Long> {
@NativeQuery("SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1")
Map<String, Object> findRawMapByEmail(String emailAddress); (1)
@NativeQuery("SELECT * FROM USERS WHERE LASTNAME = ?1")
List<Map<String, Object>> findRawMapByLastname(String lastname); (2)
}
1 | 単一の Map 結果が Tuple によってサポートされます。 |
2 | Tuple によって裏付けられた複数の Map 結果。 |
文字列ベースのタプルクエリは Hibernate でのみサポートされます。Eclipselink は条件ベースのタプルクエリのみをサポートします。 |
並べ替えの使用
ソートは、PageRequest
を提供するか、Sort
を直接使用することによって実行できます。Sort
の Order
インスタンス内で実際に使用されるプロパティは、ドメインモデルと一致する必要があります。つまり、クエリ内で使用されるプロパティまたはエイリアスのいずれかに解決する必要があります。JPQL は、これを状態フィールドパス式として定義しています。
参照不可能なパス式を使用すると、Exception になります。 |
ただし、Sort
を @Query
と組み合わせて使用すると、ORDER BY
句内の関数を含む、パスがチェックされていない Order
インスタンスに忍び込むことができます。これは、Order
が特定のクエリ文字列に追加されるため可能です。デフォルトでは、Spring Data JPA は関数呼び出しを含む Order
インスタンスを拒否しますが、JpaSort.unsafe
を使用して、潜在的に安全でない順序を追加できます。
次の例では、Sort
と JpaSort
を使用しています。JpaSort
の安全でないオプションも含まれています。
Sort
および JpaSort
の使用 public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.lastname like ?1%")
List<User> findByAndSort(String lastname, Sort sort);
@Query("select u.id, LENGTH(u.firstname) as fn_len from User u where u.lastname like ?1%")
List<Object[]> findByAsArrayAndSort(String lastname, Sort sort);
}
repo.findByAndSort("lannister", Sort.by("firstname")); (1)
repo.findByAndSort("stark", Sort.by("LENGTH(firstname)")); (2)
repo.findByAndSort("targaryen", JpaSort.unsafe("LENGTH(firstname)")); (3)
repo.findByAsArrayAndSort("bolton", Sort.by("fn_len")); (4)
1 | ドメインモデルのプロパティを指す有効な Sort 式。 |
2 | 関数呼び出しを含む無効な Sort 。例外をスローします。 |
3 | 明示的に安全でない Order を含む有効な Sort 。 |
4 | エイリアス機能を指す有効な Sort 式。 |
大きなクエリ結果のスクロール
大規模なデータセットを扱う場合、スクロールすると、すべての結果をメモリにロードせずに、結果を効率的に処理できます。
大規模なクエリ結果を使用するには、複数のオプションがあります。
ページング。前の章で
Pageable
とPageRequest
について学習しました。オフセットベースのスクロール。これは、合計結果数を必要としないため、ページングよりも軽量なバリアントです。
キーセットベースのスクロール。この方法では、データベースインデックスを利用することで、オフセットベースの結果取得の欠点 (英語) を回避します。
特定のアレンジメントに最適な方法について詳しくは、こちらを参照してください。
文字列ベースのクエリメソッドによるスクロールはまだサポートされていません。保存された @Procedure クエリメソッドを使用したスクロールもサポートされていません。 |
名前付きパラメーターの使用
上記のすべての例で説明したように、デフォルトでは、Spring Data JPA は位置ベースのパラメーターバインディングを使用します。これにより、パラメーターの位置に関するリファクタリング時に、クエリメソッドが少しエラーを起こしやすくなります。この課題を解決するには、次の例に示すように、@Param
アノテーションを使用してメソッドパラメーターに具体的な名前を付け、クエリ内の名前をバインドします。
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")
User findByLastnameOrFirstname(@Param("lastname") String lastname,
@Param("firstname") String firstname);
}
メソッドパラメーターは、定義されたクエリの順序に従って切り替えられます。 |
バージョン 4 の時点で、Spring は -parameters コンパイラーフラグに基づいて Java 8 のパラメーター名の検出を完全にサポートしています。デバッグ情報の代替としてビルドでこのフラグを使用することにより、名前付きパラメーターの @Param アノテーションを省略できます。 |
表現の使用
@Query
で定義される手動で定義されたクエリでは、制限された式の使用がサポートされています。クエリが実行されると、これらの式は定義済みの変数セットに対して評価されます。
値式に慣れていない場合は、値式の基礎を参照して SpEL 式とプロパティプレースホルダーについて学びましょう。 |
Spring Data JPA は、entityName
という変数をサポートしています。その使い方は select x from #{#entityName} x
です。指定されたリポジトリに関連付けられたドメイン型の entityName
を挿入します。entityName
は次のように解決されます: * ドメイン型が @Entity
アノテーションに name プロパティを設定している場合は、それが使用されます。* それ以外の場合は、ドメイン型の単純なクラス名が使用されます。
次の例は、クエリ文字列内の #{#entityName}
式の 1 つのユースケースを示しています。ここでは、クエリメソッドと手動で定義されたクエリでリポジトリインターフェースを定義します。
@Entity
public class User {
@Id
@GeneratedValue
Long id;
String lastname;
}
public interface UserRepository extends JpaRepository<User,Long> {
@Query("select u from #{#entityName} u where u.lastname = ?1")
List<User> findByLastname(String lastname);
}
@Query
アノテーションのクエリ文字列に実際のエンティティ名が記述されないようにするには、#{#entityName}
変数を使用できます。
entityName は、@Entity アノテーションを使用してカスタマイズできます。orm.xml のカスタマイズは、SpEL 式ではサポートされていません。 |
もちろん、クエリ宣言で User
を直接使用することもできますが、その場合もクエリを変更する必要があります。#entityName
への参照は、User
クラスの潜在的な将来の再マッピングを別のエンティティ名にピックアップします(たとえば、@Entity(name = "MyUser")
を使用して)。
クエリ文字列の #{#entityName}
式のもう 1 つの使用例は、具体的なドメイン型用の特殊なリポジトリインターフェースで汎用リポジトリインターフェースを定義する場合です。具体的なインターフェースでカスタムクエリメソッドの定義を繰り返さないようにするには、次の例に示すように、汎用リポジトリインターフェースの @Query
アノテーションのクエリ文字列でエンティティ名式を使用できます。
@MappedSuperclass
public abstract class AbstractMappedType {
…
String attribute;
}
@Entity
public class ConcreteType extends AbstractMappedType { … }
@NoRepositoryBean
public interface MappedTypeRepository<T extends AbstractMappedType>
extends Repository<T, Long> {
@Query("select t from #{#entityName} t where t.attribute = ?1")
List<T> findAllByAttribute(String attribute);
}
public interface ConcreteRepository
extends MappedTypeRepository<ConcreteType> { … }
上記の例では、MappedTypeRepository
インターフェースは、AbstractMappedType
を継承するいくつかのドメイン型の共通の親インターフェースです。また、汎用の findAllByAttribute(…)
メソッドも定義します。これは、特殊なリポジトリインターフェースのインスタンスで使用できます。ConcreteRepository
で findAllByAttribute(…)
を呼び出すと、クエリは select t from ConcreteType t where t.attribute = ?1
になります。
式を使用して引数を制御することもできます。式はメソッド引数の制御にも使用できます。これらの式ではエンティティ名は使用できませんが、引数は使用できます。次の例に示すように、名前またはインデックスでアクセスできます。
@Query("select u from User u where u.firstname = ?1 and u.firstname=?#{[0]} and u.emailAddress = ?#{principal.emailAddress}")
List<User> findByFirstnameAndCurrentUserWithCustomQuery(String firstname);
like
-conditions の場合、文字列値パラメーターの最初または最後に %
を追加したいことがよくあります。これは、バインドパラメーターマーカーまたは SpEL 式に %
を追加または接頭辞として付けることで実行できます。繰り返しますが、次の例はこれを示しています。
@Query("select u from User u where u.lastname like %:#{[0]}% and u.lastname like %:lastname%")
List<User> findByLastnameWithSpelExpression(@Param("lastname") String lastname);
安全でないソースからの値で like
-conditions を使用する場合、ワイルドカードを含めることができないように値をサニタイズする必要があります。これにより、攻撃者は必要以上のデータを選択できるようになります。この目的のために、escape(String)
メソッドが SpEL コンテキストで使用可能になります。最初の引数の _
および %
のすべてのインスタンスの前に、2 番目の引数の 1 文字を付けます。JPQL および標準 SQL で使用可能な like
式の escape
句と組み合わせることで、バインドパラメーターを簡単にクリーニングできます。
@Query("select u from User u where u.firstname like %?#{escape([0])}% escape ?#{escapeCharacter()}")
List<User> findContainingEscaped(String namePart);
リポジトリインターフェースでこのメソッド宣言を指定すると、findContainingEscaped("Peter_")
は Peter_Parker
を検出しますが、Peter Parker
は検出しません。使用するエスケープ文字は、@EnableJpaRepositories
アノテーションの escapeCharacter
を設定することで設定できます。SpEL コンテキストで使用可能なメソッド escape(String)
は、SQL および JPQL 標準のワイルドカード _
および %
のみをエスケープすることに注意してください。基盤となるデータベースまたは JPA 実装が追加のワイルドカードをサポートしている場合、これらはエスケープされません。
@Query("select u from User u where u.applicationName = ?${spring.application.name:unknown}")
List<User> findContainingEscaped(String namePart);
実行時に Environment
からプロパティを解決する場合は、クエリメソッドでフォールバックを含む構成プロパティ名を参照することもできます。プロパティはクエリ実行時に評価されます。通常、プロパティプレースホルダーは文字列のような値に解決されます。
その他の方法
Spring Data JPA は、クエリを作成するためのさまざまな方法を提供します。ただし、場合によっては、クエリが単に複雑すぎて、提供される手法が適用できない場合もあります。そのような状況では、次のことを考慮してください。
まだ作成していない場合は、
@Query
を使用して自分でクエリを作成します。それがニーズに合わない場合は、カスタム実装の実装を検討してください。これにより、実装を完全にユーザーに任せながら、リポジトリにメソッドを登録できます。これにより、次のことが可能になります。
EntityManager
と直接話す (純粋な HQL/JPQL/EQL/ ネイティブ SQL を記述するか、条件 API を使用する)Spring Framework の
JdbcTemplate
を活用する (ネイティブ SQL)別のサードパーティ製データベースツールキットを使用してください。
もう 1 つのオプションは、クエリをデータベース内に配置してから Spring Data JPA の
@StoredProcedure
アノテーションを使用するか、データベース関数の場合は@Query
アノテーションを使用してCALL
で呼び出すことです。
これらの戦術は、Spring Data JPA にリソース管理を提供しながら、クエリを最大限に制御する必要がある場合に最も効果的です。
クエリの変更
前のすべてのセクションでは、特定のエンティティまたはエンティティのコレクションにアクセスするためのクエリを宣言する方法について説明しました。Spring Data リポジトリのカスタム実装で説明されているカスタムメソッド機能を使用して、カスタムの変更動作を追加できます。このアプローチは包括的なカスタム機能に適しているため、次の例に示すように、クエリメソッドに @Modifying
アノテーションを付けることで、パラメーターバインディングのみが必要なクエリを変更できます。
@Modifying
@Query("update User u set u.firstname = ?1 where u.lastname = ?2")
int setFixedFirstnameFor(String firstname, String lastname);
これを行うと、メソッドにアノテーションが付けられたクエリが、選択クエリではなく更新クエリとしてトリガーされます。EntityManager
には変更クエリの実行後に古いエンティティが含まれる可能性があるため、自動的にクリアしません(詳細については EntityManager.clear()
の JavaDoc (英語) を参照)。これにより、EntityManager
で保留中のすべての非フラッシュ変更が効果的にドロップされます。EntityManager
を自動的にクリアする場合、@Modifying
アノテーションの clearAutomatically
属性を true
に設定できます。
@Modifying
アノテーションは、@Query
アノテーションとの組み合わせでのみ関連します。派生クエリメソッドまたはカスタムメソッドは、このアノテーションを必要としません。
派生削除クエリ
Spring Data JPA は、次の例に示すように、JPQL クエリを明示的に宣言する必要のない派生削除クエリもサポートしています。
interface UserRepository extends Repository<User, Long> {
void deleteByRoleId(long roleId);
@Modifying
@Query("delete from User u where u.role.id = ?1")
void deleteInBulkByRoleId(long roleId);
}
deleteByRoleId(…)
メソッドは、基本的に deleteInBulkByRoleId(…)
と同じ結果を生成するように見えますが、実行方法の点で 2 つのメソッド宣言には重要な違いがあります。名前が示すように、後者のメソッドは、データベースに対して単一の JPQL クエリ(アノテーションで定義されたもの)を発行します。これは、現在ロードされている User
のインスタンスでさえ、呼び出されたライフサイクルコールバックを認識しないことを意味します。
ライフサイクルクエリが実際に呼び出されることを確認するために、deleteByRoleId(…)
の呼び出しはクエリを実行し、返されたインスタンスを 1 つずつ削除して、永続性プロバイダーがそれらのエンティティで @PreRemove
コールバックを実際に呼び出すことができるようにします。
実際、派生削除クエリは、クエリを実行し、結果に対して CrudRepository.delete(Iterable<User> users)
を呼び出し、CrudRepository
の他の delete(…)
メソッドの実装との同期を維持するためのショートカットです。
多数のオブジェクトを削除する場合は、十分なメモリの可用性を確保するためにパフォーマンスへの影響を考慮する必要があります。削除されるすべてのオブジェクトは、削除される前にメモリにロードされ、フラッシュまたはトランザクションが完了するまでセッションに保持されます。 |
クエリヒントの適用
リポジトリインターフェースで宣言されたクエリに JPA クエリヒントを適用するには、@QueryHints
アノテーションを使用できます。次の例に示すように、JPA @QueryHint
アノテーションの配列とブールフラグを使用して、ページネーションを適用するときにトリガーされる追加カウントクエリに適用されるヒントを潜在的に無効にします。
public interface UserRepository extends Repository<User, Long> {
@QueryHints(value = { @QueryHint(name = "name", value = "value")},
forCounting = false)
Page<User> findByLastname(String lastname, Pageable pageable);
}
上記の宣言は、実際のクエリに構成済みの @QueryHint
を適用しますが、ページ総数を計算するためにトリガーされるカウントクエリには適用しません。
クエリへのコメントの追加
データベースのパフォーマンスに基づいてクエリをデバッグする必要がある場合があります。データベース管理者が示すクエリは、@Query
を使用して記述したものとは非常に異なって見える場合があります。または、カスタムファインダーに関して Spring Data JPA が生成したと推定するものとはまったく異なる場合があります。また、例としてクエリを使用した場合も同様です。
このプロセスを簡単にするために、@Meta
アノテーションを適用することにより、クエリまたはその他の操作に関係なく、ほとんどすべての JPA 操作にカスタムコメントを挿入できます。
@Meta
アノテーションをリポジトリ操作に適用します public interface RoleRepository extends JpaRepository<Role, Integer> {
@Meta(comment = "find roles by name")
List<Role> findByName(String name);
@Override
@Meta(comment = "find roles using QBE")
<S extends Role> List<S> findAll(Example<S> example);
@Meta(comment = "count roles for a given name")
long countByName(String name);
@Override
@Meta(comment = "exists based on QBE")
<S extends Role> boolean exists(Example<S> example);
}
このサンプルリポジトリには、カスタムファインダーが混在しているだけでなく、JpaRepository
から継承された操作をオーバーライドしています。いずれにせよ、@Meta
アノテーションを使用すると、データベースに送信される前にクエリに挿入される comment
を追加できます。
この機能はクエリだけに限定されないことに注意することも重要です。これは、count
および exists
操作にまでおよびます。また、表示されていませんが、特定の delete
操作にも拡張されます。
この機能を可能な限り適用しようとしましたが、基盤となる EntityManager の一部の操作はコメントをサポートしていません。例: entityManager.createQuery() はサポートコメントとして明確にドキュメント化されていますが、entityManager.find() 操作はそうではありません。 |
JPQL ロギングも SQL ロギングも JPA の標準ではないため、以下のセクションに示すように、各プロバイダーにはカスタム構成が必要です。
Hibernate コメントのアクティブ化
Hibernate でクエリコメントをアクティブにするには、hibernate.use_sql_comments
を true
に設定する必要があります。
Java ベースの構成設定を使用している場合、これは次のように実行できます。
@Bean
public Properties jpaProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.use_sql_comments", "true");
return properties;
}
persistence.xml
ファイルがある場合は、そこで適用できます。
persistence.xml
ベースの構成 <persistence-unit name="my-persistence-unit">
...registered classes...
<properties>
<property name="hibernate.use_sql_comments" value="true" />
</properties>
</persistence-unit>
最後に、Spring Boot を使用している場合は、application.properties
ファイル内に設定できます。
spring.jpa.properties.hibernate.use_sql_comments=true
EclipseLink コメントのアクティブ化
EclipseLink でクエリコメントをアクティブにするには、eclipselink.logging.level.sql
を FINE
に設定する必要があります。
Java ベースの構成設定を使用している場合、これは次のように実行できます。
@Bean
public Properties jpaProperties() {
Properties properties = new Properties();
properties.setProperty("eclipselink.logging.level.sql", "FINE");
return properties;
}
persistence.xml
ファイルがある場合は、そこで適用できます。
persistence.xml
ベースの構成 <persistence-unit name="my-persistence-unit">
...registered classes...
<properties>
<property name="eclipselink.logging.level.sql" value="FINE" />
</properties>
</persistence-unit>
最後に、Spring Boot を使用している場合は、application.properties
ファイル内に設定できます。
spring.jpa.properties.eclipselink.logging.level.sql=FINE
Fetch- および LoadGraphs の構成
JPA 2.1 仕様では、@NamedEntityGraph
定義を参照できる @EntityGraph
アノテーションでもサポートする Fetch- および LoadGraphs の指定のサポートが導入されました。エンティティでそのアノテーションを使用して、結果のクエリのフェッチプランを構成できます。フェッチの型(Fetch
または Load
)は、@EntityGraph
アノテーションの type
属性を使用して構成できます。詳細については、JPA 2.1 仕様 3.7.4 を参照してください。
次の例は、エンティティに名前付きエンティティグラフを定義する方法を示しています。
@Entity
@NamedEntityGraph(name = "GroupInfo.detail",
attributeNodes = @NamedAttributeNode("members"))
public class GroupInfo {
// default fetch mode is lazy.
@ManyToMany
List<GroupMember> members = new ArrayList<GroupMember>();
…
}
次の例は、リポジトリクエリメソッドで名前付きエンティティグラフを参照する方法を示しています。
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
@EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD)
GroupInfo getByGroupName(String name);
}
@EntityGraph
を使用して、アドホックエンティティグラフを定義することもできます。提供された attributePaths
は、次の例に示すように、@NamedEntityGraph
をドメイン型に明示的に追加する必要なく、対応する EntityGraph
に変換されます。
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
@EntityGraph(attributePaths = { "members" })
GroupInfo getByGroupName(String name);
}
スクロール
スクロールは、より大きな結果セットのチャンクを反復処理するための、よりきめ細かいアプローチです。スクロールは、安定ソート、スクロール型 (オフセットまたはキーセットベースのスクロール)、および結果の制限で構成されます。プロパティ名を使用して単純な並べ替え式を定義し、クエリの派生を通じて Top
または First
キーワードを使用して静的な結果制限を定義できます。式を連結して、複数の条件を 1 つの式にまとめることができます。
スクロールクエリは、アプリケーションがクエリ結果全体を処理するまで、要素のスクロール位置を取得して次の Window<T>
を取得できる Window<T>
を返します。次の結果バッチを取得して Java Iterator<List< …>>
を処理するのと同様に、クエリ結果のスクロールにより、ScrollPosition
から Window.positionAt(…)
にアクセスできます。
Window<User> users = repository.findFirst10ByLastnameOrderByFirstname("Doe", ScrollPosition.offset());
do {
for (User u : users) {
// consume the user
}
// obtain the next Scroll
users = repository.findFirst10ByLastnameOrderByFirstname("Doe", users.positionAt(users.size() - 1));
} while (!users.isEmpty() && users.hasNext());
|
上記の例は、静的な並べ替えと制限を示しています。代わりに、 |
WindowIterator
は、次の Window
の存在をチェックする必要をなくし、ScrollPosition
を適用することにより、Window
間のスクロールを簡素化するユーティリティを提供します。
WindowIterator<User> users = WindowIterator.of(position -> repository.findFirst10ByLastnameOrderByFirstname("Doe", position))
.startingAt(ScrollPosition.offset());
while (users.hasNext()) {
User u = users.next();
// consume the user
}
オフセットを使用したスクロール
オフセットスクロールは、ページネーションと同様に、オフセットカウンターを使用して多数の結果をスキップし、データソースが特定のオフセットで始まる結果のみを返すようにします。この単純なメカニズムにより、大量の結果がクライアントアプリケーションに送信されるのを回避できます。ただし、ほとんどのデータベースでは、サーバーが結果を返す前に完全なクエリ結果を具体化する必要があります。
OffsetScrollPosition
の使用 interface UserRepository extends Repository<User, Long> {
Window<User> findFirst10ByLastnameOrderByFirstname(String lastname, OffsetScrollPosition position);
}
WindowIterator<User> users = WindowIterator.of(position -> repository.findFirst10ByLastnameOrderByFirstname("Doe", position))
.startingAt(OffsetScrollPosition.initial()); (1)
1 | 位置 0 の要素を含めるには、オフセットなしで開始します。 |
|
Keyset-Filtering を使用したスクロール
オフセットベースの要件では、サーバーが結果を返す前に、ほとんどのデータベースで結果全体をマテリアライズする必要があります。そのため、クライアントはリクエストされた結果の一部しか表示しませんが、サーバーは完全な結果を構築する必要があり、追加の負荷が発生します。
Keyset-Filtering は、個々のクエリの計算と I/O 要件を削減することを目的として、データベースの組み込み機能を活用することにより、結果サブセットの取得にアプローチします。このアプローチでは、キーをクエリに渡すことでスクロールを再開するキーのセットを維持し、フィルター条件を効果的に修正します。
Keyset-Filtering の核となる考え方は、安定した並べ替え順序を使用して結果の取得を開始することです。次のチャンクにスクロールしたい場合は、並べ替えられた結果内の位置を再構築するために使用される ScrollPosition
を取得します。ScrollPosition
は、現在の Window
内の最後のエンティティのキーセットをキャプチャーします。クエリを実行するために、再構築によって条件句が書き直され、すべての並べ替えフィールドと主キーが含まれるようになります。これにより、データベースは潜在的なインデックスを利用してクエリを実行できるようになります。データベースは、指定されたキーセット位置からはるかに小さな結果を構築するだけでよく、大きな結果を完全にマテリアライズして特定のオフセットに到達するまで結果をスキップする必要はありません。
Keyset-Filtering では、キーセットプロパティ (並べ替えに使用されるもの) が null 非許容である必要があります。この制限は、比較演算子のストア固有の |
KeysetScrollPosition
の使用 interface UserRepository extends Repository<User, Long> {
Window<User> findFirst10ByLastnameOrderByFirstname(String lastname, KeysetScrollPosition position);
}
WindowIterator<User> users = WindowIterator.of(position -> repository.findFirst10ByLastnameOrderByFirstname("Doe", position))
.startingAt(ScrollPosition.keyset()); (1)
1 | 最初から開始し、追加のフィルタリングを適用しないでください。 |
Keyset-Filtering は、データベースに並べ替えフィールドに一致するインデックスが含まれている場合に最適に機能するため、静的並べ替えが適切に機能します。Keyset-Filtering を適用するスクロールクエリでは、並べ替え順序で使用されるプロパティがクエリによって返される必要があり、これらは返されるエンティティにマップされる必要があります。
インターフェースと DTO 射影を使用できますが、キーセット抽出の失敗を避けるために、並べ替えたすべてのプロパティを含めるようにしてください。
Sort
順序を指定するときは、クエリに関連する並べ替えプロパティを含めるだけで十分です。一意のクエリ結果を保証したくない場合は、その必要はありません。キーセットクエリメカニズムは、各クエリ結果が一意であることを保証するために、主キー (または複合主キーの残りの部分) を含めることによって並べ替え順序を修正します。