クエリメソッドの定義
リポジトリプロキシには、メソッド名からストア固有のクエリを派生させる 2 つの方法があります。
メソッド名から直接クエリを導出します。
手動で定義されたクエリを使用します。
利用可能なオプションは、実際のストアによって異なります。ただし、作成する実際のクエリを決定する戦略が必要です。次のセクションでは、使用可能なオプションについて説明します。
クエリ検索戦略
クエリを解決するリポジトリインフラストラクチャでは、次の戦略を使用できます。XML 構成では、query-lookup-strategy
属性を使用して名前空間で戦略を構成できます。Java 構成の場合、EnableJdbcRepositories
アノテーションの queryLookupStrategy
属性を使用できます。特定のデータストアでは一部の戦略がサポートされていない場合があります。
CREATE
は、クエリメソッド名からストア固有のクエリを作成しようとします。一般的なアプローチは、メソッド名から既知のプレフィックスの特定のセットを削除し、メソッドの残りを解析することです。クエリ構築の詳細については、"クエリ作成" を参照してください。USE_DECLARED_QUERY
は、宣言されたクエリを見つけようとし、見つからない場合は例外をスローします。クエリは、どこかのアノテーションによって定義することも、他の方法で宣言することもできます。そのストアで利用可能なオプションを見つけるには、特定のストアのドキュメントを参照してください。リポジトリインフラストラクチャがブートストラップ時にメソッドに対して宣言されたクエリを見つけられない場合、失敗します。CREATE_IF_NOT_FOUND
(デフォルト)は、CREATE
とUSE_DECLARED_QUERY
を組み合わせたものです。最初に宣言されたクエリを検索し、宣言されたクエリが見つからない場合は、カスタムメソッド名ベースのクエリを作成します。これはデフォルトのルックアップ戦略であるため、明示的に何も構成しない場合に使用されます。メソッド名によるクエリ定義をすばやく行うだけでなく、必要に応じて宣言されたクエリを導入することにより、これらのクエリをカスタム調整することもできます。
クエリ作成
Spring Data リポジトリインフラストラクチャに組み込まれているクエリビルダーメカニズムは、リポジトリのエンティティに対して制約クエリを構築できます。
次の例は、いくつかのクエリを作成する方法を示しています。
interface PersonRepository extends Repository<Person, Long> {
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
// Enables the distinct flag for the query
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
// Enabling ignoring case for an individual property
List<Person> findByLastnameIgnoreCase(String lastname);
// Enabling ignoring case for all suitable properties
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);
// Enabling static ORDER BY for a query
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}
クエリメソッド名の解析は、主語と述語に分けられます。最初の部分(find … By
、exists … By
)はクエリのサブジェクトを定義し、2 番目の部分は述語を形成します。導入句(主語)には、さらに式を含めることができます。find
(または他の導入キーワード)と By
の間のテキストは、Distinct
などの結果を制限するキーワードの 1 つを使用して、作成するクエリまたは Top
/First
は、クエリ結果を制限しますに個別のフラグを設定しない限り、説明的であると見なされます。
付録には、並べ替えや大文字小文字の修飾子を含む、クエリメソッドの主語キーワードとクエリメソッドの述語キーワードの完全なリストが含まれています。ただし、最初の By
は、実際の条件述語の開始を示す区切り文字として機能します。非常に基本的なレベルでは、エンティティプロパティの条件を定義し、And
および Or
と連結できます。
メソッドの解析の実際の結果は、クエリを作成する永続ストアによって異なります。ただし、注意すべき一般的な事項がいくつかあります。
式は通常、連結可能な演算子と組み合わせたプロパティトラバーサルです。プロパティ式を
AND
およびOR
と組み合わせることができます。プロパティ式のBetween
、LessThan
、GreaterThan
、Like
などの演算子もサポートされます。サポートされている演算子はデータストアによって異なる可能性があるため、リファレンスドキュメントの適切な部分を参照してください。メソッドパーサーは、個々のプロパティ(たとえば
findByLastnameIgnoreCase(…)
)または大文字と小文字の区別をサポートする型のすべてのプロパティ(通常はString
インスタンス -findByLastnameAndFirstnameAllIgnoreCase(…)
など)のIgnoreCase
フラグの設定をサポートします。ケースの無視がサポートされているかどうかはストアによって異なるため、ストア固有のクエリメソッドについては、リファレンスドキュメントの関連セクションを参照してください。プロパティを参照するクエリメソッドに
OrderBy
句を追加し、並べ替え方向(Asc
またはDesc
)を提供することにより、静的な順序を適用できます。動的な並べ替えをサポートするクエリメソッドを作成するには、"ページング、大規模な結果の反復、並べ替えと制限" を参照してください。
予約メソッド名
派生リポジトリメソッドは名前によってプロパティにバインドされますが、識別子プロパティをターゲットとするベースリポジトリから継承された特定のメソッド名に関しては、このルールにいくつかの例外があります。CrudRepository#findById
(または単に findById
) などの予約済みメソッドは、宣言されたメソッドで使用される実際のプロパティ名に関係なく、識別子プロパティをターゲットとします。
@Id
を介して識別子としてマークされたプロパティ pk
と、id
と呼ばれるプロパティを保持する次のドメイン型について考えます。この場合、ルックアップメソッドの名前付けは、定義済みのシグネチャーと衝突する可能性があるため、細心の注意を払う必要があります。
class User {
@Id Long pk; (1)
Long id; (2)
// …
}
interface UserRepository extends Repository<User, Long> {
Optional<User> findById(Long id); (3)
Optional<User> findByPk(Long pk); (4)
Optional<User> findUserById(Long id); (5)
}
1 | 識別子プロパティ (主キー)。 |
2 | id という名前のプロパティですが、識別子ではありません。 |
3 | pk プロパティ (識別子と見なされる @Id でマークされているもの) を対象とします。これは、CrudRepository ベースリポジトリメソッドを参照します。これは、予約済みメソッドの 1 つであるため、プロパティ名が示すように、id を使用した派生クエリではありません。 |
4 | 派生クエリであるため、pk プロパティを名前でターゲットします。 |
5 | 予約済みメソッドとの衝突を回避するために、find と by 間の記述トークンを使用して id プロパティをターゲットにします。 |
この特別な動作はルックアップメソッドを対象とするだけでなく、exits
および delete
メソッドにも適用されます。メソッドのリストについては、"リポジトリクエリキーワード" を参照してください。
プロパティ式
前の例に示すように、プロパティ式は管理対象エンティティの直接プロパティのみを参照できます。クエリの作成時に、解析されたプロパティが管理対象ドメインクラスのプロパティであることをすでに確認しています。ただし、ネストされたプロパティを走査して制約を定義することもできます。次のメソッドシグネチャーを検討してください。
List<Person> findByAddressZipCode(ZipCode zipCode);
Person
に ZipCode
を含む Address
があると仮定します。その場合、メソッドは x.address.zipCode
プロパティトラバーサルを作成します。解決アルゴリズムは、パーツ全体(AddressZipCode
)をプロパティとして解釈することから始まり、ドメインクラスでその名前(大文字でない)のプロパティをチェックします。アルゴリズムが成功すると、そのプロパティが使用されます。そうでない場合、アルゴリズムはキャメルケース部分のソースを右側から頭と尾に分割し、対応するプロパティ(この例では、AddressZip
と Code
)を見つけようとします。アルゴリズムがそのヘッドを持つプロパティを見つけると、テールを取得し、そこからツリーを構築し続け、今説明したメソッドでテールを分割します。最初の分割が一致しない場合、アルゴリズムは分割ポイントを左に移動し(Address
、ZipCode
)、続行します。
これはほとんどの場合に機能するはずですが、アルゴリズムが間違ったプロパティを選択する可能性があります。Person
クラスにも addressZip
プロパティがあるとします。アルゴリズムは最初の分割ラウンドですでに一致し、間違ったプロパティを選択して失敗します(addressZip
の型にはおそらく code
プロパティがないため)。
このあいまいさを解決するには、メソッド名内で _
を使用して、トラバーサルポイントを手動で定義します。メソッド名は次のようになります。
List<Person> findByAddress_ZipCode(ZipCode zipCode);
アンダースコア ( |
アンダースコアで始まるフィールド名: フィールド名は、 大文字のフィールド名: すべて大文字のフィールド名はそのまま使用できます。ネストされたパス (該当する場合) は、 2 番目の大文字を含むフィールド名:
パスの曖昧さ: 次のサンプルでは、プロパティ
プロパティの直接一致が最初に考慮されるため、潜在的なネストされたパスは考慮されず、アルゴリズムは |
コレクションまたはイテラブルを返すリポジトリメソッド
複数の結果を返すクエリメソッドは、標準の Java Iterable
、List
、Set
を使用できます。さらに、Spring Data の Streamable
、Iterable
のカスタム拡張、Vavr (英語) によって提供されるコレクション型を返すことをサポートします。考えられるすべてのクエリメソッドの戻り値の型について説明している付録を参照してください。
Streamable をクエリメソッドの戻り値の型として使用する
Iterable
または任意のコレクション型の代わりに Streamable
を使用できます。これは、非並列 Stream
(Iterable
にはない)にアクセスするための便利なメソッドと、要素を介して … .filter(…)
および … .map(…)
を直接アクセスし、Streamable
を他の要素に連結する機能を提供します。
interface PersonRepository extends Repository<Person, Long> {
Streamable<Person> findByFirstnameContaining(String firstname);
Streamable<Person> findByLastnameContaining(String lastname);
}
Streamable<Person> result = repository.findByFirstnameContaining("av")
.and(repository.findByLastnameContaining("ea"));
カスタムのストリーミング可能なラッパー型を返す
コレクション専用のラッパー型を提供することは、複数の要素を返すクエリ結果の API を提供するために一般的に使用されるパターンです。通常、これらの型は、コレクションのような型を返すリポジトリメソッドを呼び出し、ラッパー型のインスタンスを手動で作成することによって使用されます。Spring Data では、次の条件を満たす場合、これらのラッパー型をクエリメソッドの戻り値の型として使用できるため、この追加の手順を回避できます。
型は
Streamable
を実装します。この型は、
Streamable
を引数として取るof(…)
またはvalueOf(…)
という名前のコンストラクターまたは静的ファクトリメソッドのいずれかを公開します。
次のリストに例を示します。
class Product { (1)
MonetaryAmount getPrice() { … }
}
@RequiredArgsConstructor(staticName = "of")
class Products implements Streamable<Product> { (2)
private final Streamable<Product> streamable;
public MonetaryAmount getTotal() { (3)
return streamable.stream()
.map(Product::getPrice)
.reduce(Money.of(0), MonetaryAmount::add);
}
@Override
public Iterator<Product> iterator() { (4)
return streamable.iterator();
}
}
interface ProductRepository implements Repository<Product, Long> {
Products findAllByDescriptionContaining(String text); (5)
}
1 | 製品の価格にアクセスするための API を公開する Product エンティティ。 |
2 | Products.of(…) (Lombok アノテーションを使用して作成されたファクトリメソッド)を使用して構築できる Streamable<Product> のラッパー型。Streamable<Product> を使用する標準のコンストラクターも同様に機能します。 |
3 | ラッパー型は追加の API を公開し、Streamable<Product> で新しい値を計算します。 |
4 | Streamable インターフェースを実装し、実際の結果に委譲します。 |
5 | そのラッパー型 Products は、クエリメソッドの戻り値の型として直接使用できます。Streamable<Product> を返し、リポジトリクライアントでクエリの後に手動でラップする必要はありません。 |
Vavr コレクションのサポート
Vavr (英語) は、Java の関数型プログラミングの概念を取り入れたライブラリです。次の表に示すように、クエリメソッドの戻り値の型として使用できるコレクション型のカスタムセットが付属しています。
Vavr コレクション型 | 使用される Vavr 実装型 | 有効な Java ソース型 |
---|---|---|
|
|
|
|
|
|
|
|
|
実際のクエリ結果の Java 型(3 番目の列)に応じて、最初の列の型(またはそのサブ型)をクエリメソッドの戻り値の型として使用し、実装型として使用される 2 番目の列の型を取得できます。または、Traversable
(Vavr Iterable
と同等)を宣言して、実際の戻り値から実装クラスを導出することもできます。つまり、java.util.List
は Vavr List
または Seq
に変換され、java.util.Set
は Vavr LinkedHashSet
Set
に変換されます。
クエリ結果のストリーミング
戻り値の型として Java 8 Stream<T>
を使用することにより、クエリメソッドの結果を段階的に処理できます。次の例に示すように、クエリ結果を Stream
でラップする代わりに、データストア固有のメソッドを使用してストリーミングを実行します。
Stream<T>
を使用したクエリの結果のストリーミング @Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();
Stream<User> readAllByFirstnameNotNull();
@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);
Stream は潜在的に基礎となるデータストア固有のリソースをラップするため、使用後に閉じる必要があります。次の例に示すように、close() メソッドを使用するか、Java 7 try-with-resources ブロックを使用して、Stream を手動で閉じることができます。 |
Stream<T>
を操作すると、try-with-resources
ブロックが生成されます try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
stream.forEach(…);
}
現在、すべての Spring Data モジュールが戻り型として Stream<T> をサポートしているわけではありません。 |
非同期クエリ結果
Spring の非同期メソッド実行機能を使用すると、リポジトリクエリを非同期で実行できます。これは、Spring TaskExecutor
に送信されたタスクで実際のクエリが発生している間、メソッドは呼び出し直後に戻ることを意味します。非同期クエリはリアクティブクエリとは異なるため、混在させないでください。リアクティブサポートの詳細については、ストア固有のドキュメントを参照してください。次の例は、いくつかの非同期クエリを示しています。
@Async
Future<User> findByFirstname(String firstname); (1)
@Async
CompletableFuture<User> findOneByFirstname(String firstname); (2)
1 | 戻り値の型として java.util.concurrent.Future を使用します。 |
2 | 戻り値の型として Java 8 java.util.concurrent.CompletableFuture を使用します。 |
ページング、大規模な結果の反復、並べ替えと制限
クエリ内のパラメーターを処理するには、前述の例で示したようにメソッドパラメーターを定義します。それに加えて、インフラストラクチャは Pageable
、Sort
、Limit
などの特定の型を認識し、ページネーション、並べ替え、制限をクエリに動的に適用します。次の例は、これらの機能を示しています。
Pageable
、Slice
、Sort
、Limit
の使用 Page<User> findByLastname(String lastname, Pageable pageable);
Slice<User> findByLastname(String lastname, Pageable pageable);
List<User> findByLastname(String lastname, Sort sort);
List<User> findByLastname(String lastname, Sort sort, Limit limit);
List<User> findByLastname(String lastname, Pageable pageable);
Sort 、Pageable 、Limit を使用する API は、非 null 値がメソッドに渡されることを想定しています。並べ替えやページネーションを適用したくない場合は、Sort.unsorted() 、Pageable.unpaged() 、Limit.unlimited() を使用します。 |
最初のメソッドでは、org.springframework.data.domain.Pageable
インスタンスをクエリメソッドに渡して、静的に定義されたクエリにページングを動的に追加できます。Page
は、使用可能な要素とページの総数を認識しています。これは、インフラストラクチャがカウントクエリをトリガーして全体の数を計算することによって行われます。これは(使用するストアによっては)高額になる可能性があるため、代わりに Slice
を返すことができます。Slice
は、次の Slice
が使用可能かどうかのみを認識します。これは、より大きな結果セットをウォークスルーする場合に十分な場合があります。
並べ替えオプションも Pageable
インスタンスを介して処理されます。並べ替えのみが必要な場合は、メソッドに org.springframework.data.domain.Sort
パラメーターを追加します。ご覧のとおり、List
を返すことも可能です。この場合、実際の Page
インスタンスを構築するために必要な追加のメタデータは作成されません(つまり、必要だったはずの追加のカウントクエリは発行されません)。むしろ、指定された範囲のエンティティのみを検索するようにクエリを制限します。
クエリ全体で取得するページ数を調べるには、追加のカウントクエリをトリガーする必要があります。デフォルトでは、このクエリは実際にトリガーするクエリから派生します。 |
特別なパラメーターは、クエリメソッド内で 1 回のみ使用できます。
The |
Which Method is Appropriate?
The value provided by the Spring Data abstractions is perhaps best shown by the possible query method return types outlined in the following table below. The table shows which types you can return from a query method
Method | Amount of Data Fetched | Query Structure | Constraints |
---|---|---|---|
All results. |
Single query. |
Query results can exhaust all memory. Fetching all data can be time-intensive. |
|
All results. |
Single query. |
Query results can exhaust all memory. Fetching all data can be time-intensive. |
|
Chunked (one-by-one or in batches) depending on |
Single query using typically cursors. |
Streams must be closed after usage to avoid resource leaks. |
|
|
| 通常はカーソルを使用する単一のクエリ。 | Store モジュールはリアクティブインフラストラクチャを提供する必要があります。 |
|
| 制限を適用して |
|
|
| 制限を適用する | 多くの場合、コストのかかる
|
ページングとソート
プロパティ名を使用して、簡単な並べ替え式を定義できます。式を連結して、複数の条件を 1 つの式に集めることができます。
Sort sort = Sort.by("firstname").ascending()
.and(Sort.by("lastname").descending());
ソート式を定義するためのより型安全な方法については、ソート式を定義する型から始め、メソッド参照を使用してソートするプロパティを定義します。
TypedSort<Person> person = Sort.sort(Person.class);
Sort sort = person.by(Person::getFirstname).ascending()
.and(person.by(Person::getLastname).descending());
TypedSort.by(…) は、(通常)CGlib を使用してランタイムプロキシを利用します。これは、Graal VMNative などのツールを使用するときにネイティブイメージのコンパイルを妨げる可能性があります。 |
ストアの実装が Querydsl をサポートしている場合は、生成されたメタモデル型を使用して、並べ替え式を定義することもできます。
QSort sort = QSort.by(QPerson.firstname.asc())
.and(QSort.by(QPerson.lastname.desc()));
クエリ結果の制限
ページングに加えて、専用の Limit
パラメーターを使用して結果のサイズを制限することができます。First
または Top
キーワードを使用して、クエリメソッドの結果を制限することもできます。キーワードは同じ意味で使用できますが、Limit
パラメーターと混合することはできません。Top
または First
にオプションの数値を追加して、返される結果の最大サイズを指定できます。数値を省略した場合、結果のサイズは 1 とみなされます。次の例は、クエリサイズを制限する方法を示しています。
Top
および First
を使用したクエリの結果サイズの制限 List<User> findByLastname(Limit limit);
User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
制限式は、個別のクエリをサポートするデータストアの Distinct
キーワードもサポートします。また、結果セットを 1 つのインスタンスに制限するクエリの場合、結果を Optional
キーワードでラップすることがサポートされています。
ページネーションまたはスライスが制限クエリページネーション(および使用可能なページ数の計算)に適用される場合、制限された結果内で適用されます。
Sort パラメーターを使用して動的ソートと組み合わせて結果を制限すると、"K" 最小エレメントと "K" 最大エレメントの照会メソッドを表現できます。 |