例示による問い合わせ

導入

この章では、Query by Example の概要とその使用方法について説明します。

Query by Example(QBE)は、シンプルなインターフェースを備えた使いやすいクエリ手法です。動的なクエリの作成が可能になり、フィールド名を含むクエリを作成する必要がなくなります。実際、Query by Example では、ストア固有のクエリ言語を使用してクエリを記述する必要はまったくありません。

この章では、Query by Example の中心的な概念について説明します。情報は Spring Data Commons モジュールから取得されます。データベースによっては、文字列一致のサポートが制限される場合があります。

使用方法

例示による問い合わせ API は、次の 4 つの部分で構成されています。

  • プローブ: フィールドが設定されたドメインオブジェクトの実際の例。

  • ExampleMatcherExampleMatcher には、特定のフィールドの照合方法に関する詳細が記載されています。複数の例で再利用できます。

  • ExampleExample は、プローブと ExampleMatcher で構成されています。クエリの作成に使用されます。

  • FetchableFluentQueryFetchableFluentQuery は、Example から派生したクエリをさらにカスタマイズできる流れるような API を提供します。Fluent API を使用すると、クエリの順序付け射影と結果処理を指定できます。

例示による問い合わせは、いくつかのユースケースに適しています。

  • 静的または動的な制約のセットを使用してデータストアをクエリします。

  • 既存のクエリを壊すことを心配せずにドメインオブジェクトを頻繁にリファクタリングします。

  • 基盤となるデータストア API から独立して動作します。

例示による問い合わせには、いくつかの制限もあります。

  • firstname = ?0 or (firstname = ?1 and lastname = ?2) など、ネストまたはグループ化されたプロパティ制約はサポートされていません。

  • 文字列マッチングにおけるストア固有のサポート。データベースによっては、文字列一致では文字列の開始 / 含む / 終了 / 正規表現をサポートできます。

  • 他のプロパティ型と完全に一致します。

Query by Example を開始する前に、ドメインオブジェクトが必要です。開始するには、次の例に示すように、リポジトリのインターフェースを作成します。

サンプル Person オブジェクト
public class Person {

  @Id
  private String id;
  private String firstname;
  private String lastname;
  private Address address;

  // … getters and setters omitted
}

前の例は、単純なドメインオブジェクトを示しています。これを使用して Example を作成できます。デフォルトでは、null 値を持つフィールドは無視され、文字列はストア固有のデフォルトを使用して照合されます。

例示による問い合わせ条件へのプロパティの包含は、null 可能性に基づいています。プリミティブ型(intdouble、…)を使用するプロパティは、ExampleMatcher はプロパティパスを無視しますでない限り、常に含まれます。

例は、of ファクトリメソッドを使用するか、ExampleMatcher を使用して作成できます。Example は不変です。次のリストは、簡単な例を示しています。

例 1: 簡単な例
Person person = new Person();                         (1)
person.setFirstname("Dave");                          (2)

Example<Person> example = Example.of(person);         (3)
1 ドメインオブジェクトの新しいインスタンスを作成します。
2 クエリにプロパティを設定します。
3Example を作成します。

リポジトリを使用して、サンプルクエリを実行できます。これを行うには、リポジトリインターフェースに 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 処理、プロパティ固有の設定に独自のデフォルトを指定できます。

例 2: カスタマイズされたマッチングを使用したマッチャーの例
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 セットのプロパティ。
3ExampleMatcher を作成して、すべての値が一致することを期待します。この段階では、さらに構成しなくても使用できます。
4lastname プロパティパスを無視する新しい 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 設定の範囲を説明しています。

表 1: ExampleMatcher 設定の範囲
設定 スコープ

null ハンドリング

ExampleMatcher

文字列マッチング

ExampleMatcher とプロパティパス

プロパティを無視する

プロパティパス

大文字と小文字の区別

ExampleMatcher とプロパティパス

値変換

プロパティパス

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 は、照会される属性を制限します。firstfirstValueoneoneValueallpagestreamcountexists は、取得する結果の種類と、予想よりも多くの結果が利用可能な場合のクエリの動作を定義します。

流れるような API を使用して、潜在的に多くの結果の最後を姓順に取得します。
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 フィールドを照合します)
2name フィールドには、フィールドの終わりと一致するワイルドカードを使用します
3 列を null と照合します(リレーショナルデータベースでは、NULL が NULL と等しくないことを忘れないでください)。
4 クエリを作成するときは、role フィールドを無視してください。
5 カスタム ExampleMatcher をプローブに接続します。

任意のプロパティに対して withTransform() を適用して、クエリを作成する前にプロパティを変換することもできます。例: クエリが作成される前に、toUpperCase() を String -based プロパティに適用できます。

Query By Example は、クエリに必要なすべてのフィールドが事前にわからない場合に真価を発揮します。ユーザーがフィールドを選択できる Web ページ上にフィルターを構築している場合、Query By Example は、それを効率的なクエリに柔軟に取り込むための優れた方法です。