例示による問い合わせ
導入
この章では、Query by Example の概要とその使用方法について説明します。
Query by Example(QBE)は、シンプルなインターフェースを備えた使いやすいクエリ手法です。動的なクエリの作成が可能になり、フィールド名を含むクエリを作成する必要がなくなります。実際、Query by Example では、ストア固有のクエリ言語を使用してクエリを記述する必要はまったくありません。
この章では、Query by Example の中心的な概念について説明します。情報は Spring Data Commons モジュールから取得されます。データベースによっては、文字列一致のサポートが制限される場合があります。 |
使用方法
例示による問い合わせ API は、次の 4 つの部分で構成されています。
プローブ: フィールドが設定されたドメインオブジェクトの実際の例。
ExampleMatcher
:ExampleMatcher
には、特定のフィールドの照合方法に関する詳細が記載されています。複数の例で再利用できます。Example
:Example
は、プローブとExampleMatcher
で構成されています。クエリの作成に使用されます。FetchableFluentQuery
:FetchableFluentQuery
は、Example
から派生したクエリをさらにカスタマイズできる流れるような API を提供します。Fluent API を使用すると、クエリの順序付け射影と結果処理を指定できます。
例示による問い合わせは、いくつかのユースケースに適しています。
静的または動的な制約のセットを使用してデータストアをクエリします。
既存のクエリを壊すことを心配せずにドメインオブジェクトを頻繁にリファクタリングします。
基盤となるデータストア API から独立して動作します。
例示による問い合わせには、いくつかの制限もあります。
firstname = ?0 or (firstname = ?1 and lastname = ?2)
など、ネストまたはグループ化されたプロパティ制約はサポートされていません。文字列マッチングにおけるストア固有のサポート。データベースによっては、文字列一致では文字列の開始 / 含む / 終了 / 正規表現をサポートできます。
他のプロパティ型と完全に一致します。
Query by Example を開始する前に、ドメインオブジェクトが必要です。開始するには、次の例に示すように、リポジトリのインターフェースを作成します。
public class Person {
@Id
private String id;
private String firstname;
private String lastname;
private Address address;
// … getters and setters omitted
}
前の例は、単純なドメインオブジェクトを示しています。これを使用して Example
を作成できます。デフォルトでは、null
値を持つフィールドは無視され、文字列はストア固有のデフォルトを使用して照合されます。
例示による問い合わせ条件へのプロパティの包含は、null 可能性に基づいています。プリミティブ型(int 、double 、…)を使用するプロパティは、ExampleMatcher はプロパティパスを無視しますでない限り、常に含まれます。 |
例は、of
ファクトリメソッドを使用するか、ExampleMatcher
を使用して作成できます。Example
は不変です。次のリストは、簡単な例を示しています。
Person person = new Person(); (1)
person.setFirstname("Dave"); (2)
Example<Person> example = Example.of(person); (3)
1 | ドメインオブジェクトの新しいインスタンスを作成します。 |
2 | クエリにプロパティを設定します。 |
3 | Example を作成します。 |
リポジトリを使用して、サンプルクエリを実行できます。これを行うには、リポジトリインターフェースに QueryByExampleExecutor<T>
を継承させます。次のリストは、QueryByExampleExecutor
インターフェースからの抜粋を示しています。
QueryByExampleExecutor
public interface QueryByExampleExecutor<T> {
<S extends T> S findOne(Example<S> example);
<S extends T> Iterable<S> findAll(Example<S> example);
// … more functionality omitted.
}
マッチャーの例
例はデフォルト設定に限定されません。次の例に示すように、ExampleMatcher
を使用して、文字列照合、null 処理、プロパティ固有の設定に独自のデフォルトを指定できます。
Person person = new Person(); (1)
person.setFirstname("Dave"); (2)
ExampleMatcher matcher = ExampleMatcher.matching() (3)
.withIgnorePaths("lastname") (4)
.withIncludeNullValues() (5)
.withStringMatcher(StringMatcher.ENDING); (6)
Example<Person> example = Example.of(person, matcher); (7)
1 | ドメインオブジェクトの新しいインスタンスを作成します。 |
2 | セットのプロパティ。 |
3 | ExampleMatcher を作成して、すべての値が一致することを期待します。この段階では、さらに構成しなくても使用できます。 |
4 | lastname プロパティパスを無視する新しい ExampleMatcher を構築します。 |
5 | 新しい ExampleMatcher を作成して、lastname プロパティパスを無視し、null 値を含めます。 |
6 | 新しい ExampleMatcher を作成して、lastname プロパティパスを無視し、null 値を含め、サフィックス文字列の照合を実行します。 |
7 | ドメインオブジェクトと設定された ExampleMatcher に基づいて新しい Example を作成します。 |
デフォルトでは、ExampleMatcher
はプローブに設定されたすべての値が一致することを期待しています。暗黙的に定義された述語のいずれかに一致する結果を取得する場合は、ExampleMatcher.matchingAny()
を使用します。
個々のプロパティ(「名」や「姓」、ネストされたプロパティの場合は "address.city" など)の動作を指定できます。次の例に示すように、一致するオプションと大文字と小文字の区別を使用して調整できます。
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("firstname", endsWith())
.withMatcher("lastname", startsWith().ignoreCase());
}
マッチャーオプションを構成する別の方法は、ラムダ(Java 8 で導入)を使用することです。このアプローチは、実装者にマッチャーの変更を要求するコールバックを作成します。設定オプションはマッチャーインスタンス内に保持されているため、マッチャーを返す必要はありません。次の例は、ラムダを使用するマッチャーを示しています。
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("firstname", match -> match.endsWith())
.withMatcher("firstname", match -> match.startsWith());
}
Example
によって作成されたクエリは、構成の統合ビューを使用します。デフォルトのマッチング設定は ExampleMatcher
レベルで設定できますが、個々の設定は特定のプロパティパスに適用できます。ExampleMatcher
で設定された設定は、明示的に定義されていない限り、プロパティパス設定に継承されます。プロパティパッチの設定は、デフォルト設定よりも優先されます。次の表は、さまざまな ExampleMatcher
設定の範囲を説明しています。
設定 | スコープ |
---|---|
null ハンドリング |
|
文字列マッチング |
|
プロパティを無視する | プロパティパス |
大文字と小文字の区別 |
|
値変換 | プロパティパス |
Fluent API
QueryByExampleExecutor
は、これまでメンションしなかったもう 1 つの方法、<S extends T, R> R findBy(Example<S> example, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction)
を提供します。他のメソッドと同様に、Example
から派生したクエリを実行します。ただし、2 番目の引数を使用すると、他の方法では動的に制御できない実行の側面を制御できます。これを行うには、2 番目の引数で FetchableFluentQuery
のさまざまなメソッドを呼び出します。sortBy
を使用すると、結果の順序を指定できます。as
では、結果を変換する型を指定できます。project
は、照会される属性を制限します。first
、firstValue
、one
、oneValue
、all
、page
、stream
、count
、exists
は、取得する結果の種類と、予想よりも多くの結果が利用可能な場合のクエリの動作を定義します。
Optional<Person> match = repository.findBy(example,
q -> q
.sortBy(Sort.by("lastname").descending())
.first()
);
次に例を示します。
Employee employee = new Employee(); (1)
employee.name= "Frodo";
Example<Employee> example = Example.of(employee); (2)
repository.findAll(example); (3)
// do whatever with the result
1 | 条件を使用してドメインオブジェクトを作成します(null フィールドは無視されます)。 |
2 | ドメインオブジェクトを使用して、Example を作成します。 |
3 | リポジトリを介して、クエリを実行します (単一アイテムに対して findOne を使用します)。 |
これは、ドメインオブジェクトを使用して単純なプローブを作成する方法を示しています。この場合、Frodo
と等しい Employee
オブジェクトの name
フィールドに基づいてクエリを実行します。null
フィールドは無視されます。
Employee employee = new Employee();
employee.name = "Baggins";
employee.role = "ring bearer";
ExampleMatcher matcher = matching() (1)
.withMatcher("name", endsWith()) (2)
.withIncludeNullValues() (3)
.withIgnorePaths("role"); (4)
Example<Employee> example = Example.of(employee, matcher); (5)
repository.findAll(example);
// do whatever with the result
1 | すべてのフィールドに一致するカスタム ExampleMatcher を作成します (matchingAny() を使用して ANY フィールドを照合します) |
2 | name フィールドには、フィールドの終わりと一致するワイルドカードを使用します |
3 | 列を null と照合します(リレーショナルデータベースでは、NULL が NULL と等しくないことを忘れないでください)。 |
4 | クエリを作成するときは、role フィールドを無視してください。 |
5 | カスタム ExampleMatcher をプローブに接続します。 |
任意のプロパティに対して withTransform()
を適用して、クエリを作成する前にプロパティを変換することもできます。例: クエリが作成される前に、toUpperCase()
を String
-based プロパティに適用できます。
Query By Example は、クエリに必要なすべてのフィールドが事前にわからない場合に真価を発揮します。ユーザーがフィールドを選択できる Web ページ上にフィルターを構築している場合、Query By Example は、それを効率的なクエリに柔軟に取り込むための優れた方法です。