例示による問い合わせ

導入

この章では、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 は、findBy(Example<S> example, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) を介して Example インスタンスに基づくクエリを柔軟に実行するための流れるようなクエリメソッドを定義します。

他のメソッドと同様に、Example から派生したクエリを実行します。ただし、クエリ関数を使用すると、他の方法では動的に制御できないクエリ実行の側面を制御できます。これは、FetchableFluentQuery のさまざまな中間メソッドと終端メソッドを呼び出すことによって実現されます。

中間的な方法

  • sortBy: 結果に順序付けを適用します。メソッドを繰り返し呼び出すと、各 Sort が追加されます(ソート済みの Pageable を使用する page(Pageable) は、以前のソート順序を上書きすることに注意してください)。

  • limit: 結果数を制限します。

  • as: 読み取るまたは投影する型を指定します。

  • project: クエリのプロパティを制限します。

ターミナル方式

  • firstfirstValue: 最初の値を返します。クエリが結果を返さなかった場合、first は Optional<T> または Optional.empty() を返します。firstValue は、Optional を使用する必要がない、null 値可能なバリアントです。

  • oneoneValue: 1 つの値を返します。クエリが結果を返さなかった場合、one は Optional<T> または Optional.empty() を返します。oneValue は、Optional を使用する必要がない、NULL 値許容型のバリアントです。複数の一致が見つかった場合は、IncorrectResultSizeDataAccessException がスローされます。

  • all: すべての結果を List<T> として返します。

  • page(Pageable): すべての結果を Page<T> として返します。

  • slice(Pageable): すべての結果を Slice<T> として返します。

  • scroll(ScrollPosition): スクロール (オフセット、キーセット) を使用して、結果を Window<T> として取得します。

  • stream(): 結果を遅延処理するために Stream<T> を返します。ストリームはステートフルであり、使用後は閉じる必要があります。

  • count および exists: 一致するエンティティの数、または一致するものが存在するかどうかを返します。

中間メソッドとターミナルメソッドは、クエリ関数内で呼び出す必要があります。
例 3: Fluent API を使用して、lastname で順序付けられた投影された Page を取得します。
Page<CustomerProjection> page = repository.findBy(example,
    q -> q.as(CustomerProjection.class)
          .page(PageRequest.of(0, 20, Sort.by("lastname")))
);
例 4: 流れるような API を使用して、lastname 順に並べられた多数の結果の最後を取得します。
Optional<Customer> match = repository.findBy(example,
    q -> q.sortBy(Sort.by("lastname").descending())
          .first()
);