1. 序章

このチュートリアルでは、 Spring Data Query by ExampleAPIを使用してデータをクエリする方法を学習します。

まず、クエリするデータのスキーマを定義します。 次に、Spring Dataの関連するクラスのいくつかを調べます。 次に、いくつかの例を実行します。

始めましょう!

2. テストデータ

私たちのテストデータは、乗客の名前と乗客の座席のリストです。

ファーストネーム 苗字 座席番号
ジル スミス 50
イブ ジャクソン 94
フレッド Bloggs 22
リッキ ボビー 36
シヤ コリシ 85

3. ドメイン

必要なSpring Dataリポジトリを作成し、ドメインクラスとIDタイプを提供しましょう。

まず、PassengerをJPAエンティティとしてモデル化しました。

@Entity
class Passenger {

    @Id
    @GeneratedValue
    @Column(nullable = false)
    private Long id;

    @Basic(optional = false)
    @Column(nullable = false)
    private String firstName;

    @Basic(optional = false)
    @Column(nullable = false)
    private String lastName;

    @Basic(optional = false)
    @Column(nullable = false)
    private int seatNumber;

    // constructor, getters etc.
}

JPAを使用する代わりに、別の抽象化としてモデル化することもできます。

4. 例によるクエリAPI

まず、JpaRepositoryインターフェースを見てみましょう。 ご覧のとおり、 QueryByExampleExecutor インターフェイスを拡張して、例によるクエリをサポートしています。

public interface JpaRepository<T, ID>
  extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {}

このインターフェースは、Spring Dataでおなじみの find()メソッドのバリエーションをさらに紹介します。 ただし、各メソッドはExampleのインスタンスも受け入れます。

public interface QueryByExampleExecutor<T> {
    <S extends T> Optional<S> findOne(Example<S> var1);
    <S extends T> Iterable<S> findAll(Example<S> var1);
    <S extends T> Iterable<S> findAll(Example<S> var1, Sort var2);
    <S extends T> Page<S> findAll(Example<S> var1, Pageable var2);
    <S extends T> long count(Example<S> var1);
    <S extends T> boolean exists(Example<S> var1);
}

次に、 Example インターフェースは、probeおよびExampleMatcherにアクセスするためのメソッドを公開します。

プローブエンティティのインスタンスであることを理解することが重要です。

public interface Example<T> {

    static <T> org.springframework.data.domain.Example<T> of(T probe) {
        return new TypedExample(probe, ExampleMatcher.matching());
    }

    static <T> org.springframework.data.domain.Example<T> of(T probe, ExampleMatcher matcher) {
        return new TypedExample(probe, matcher);
    }

    T getProbe();

    ExampleMatcher getMatcher();

    default Class<T> getProbeType() {
        return ProxyUtils.getUserClass(this.getProbe().getClass());
    }
}

要約すると、プローブExampleMatcherは一緒にクエリを指定します。

5. 制限事項

すべてのものと同様に、Query byExampleAPIにはいくつかの制限があります。 例えば:

  • ネストおよびグループ化ステートメントはサポートされていません。例: firstName =?0および lastName =?1)または seatNumber [X149X ] =?2
  • 文字列照合には、正確な大文字と小文字を区別しない、開始、終了、含む、および正規表現のみが含まれます
  • String以外のすべてのタイプは完全一致のみです

APIとその制限についてもう少し詳しく理解できたので、いくつかの例を見ていきましょう。

6. 例

6.1. 大文字と小文字を区別するマッチング

簡単な例から始めて、デフォルトの動作について説明しましょう。

@Test
public void givenPassengers_whenFindByExample_thenExpectedReturned() {
    Example<Passenger> example = Example.of(Passenger.from("Fred", "Bloggs", null));

    Optional<Passenger> actual = repository.findOne(example);

    assertTrue(actual.isPresent());
    assertEquals(Passenger.from("Fred", "Bloggs", 22), actual.get());
}

特に、静的 Example.of()メソッドは、 ExampleMatcher.matching()を使用してExampleを構築します。

つまり、完全一致は、Passengerのすべての非nullプロパティで実行されます。 したがって、マッチングではStringプロパティで大文字と小文字が区別されます。

ただし、null以外のすべてのプロパティを完全に一致させるだけであれば、あまり役に立ちません。

これがExampleMatcherの出番です。 独自のExampleMatcherを作成することで、ニーズに合わせて動作をカスタマイズできます。

6.2. 大文字と小文字を区別しないマッチング

そのことを念頭に置いて、別の例を見てみましょう。今回は withIgnoreCase()を使用して、大文字と小文字を区別しないマッチングを実現します。

@Test
public void givenPassengers_whenFindByExampleCaseInsensitiveMatcher_thenExpectedReturned() {
    ExampleMatcher caseInsensitiveExampleMatcher = ExampleMatcher.matchingAll().withIgnoreCase();
    Example<Passenger> example = Example.of(Passenger.from("fred", "bloggs", null),
      caseInsensitiveExampleMatcher);

    Optional<Passenger> actual = repository.findOne(example);

    assertTrue(actual.isPresent());
    assertEquals(Passenger.from("Fred", "Bloggs", 22), actual.get());
}

この例では、最初に ExampleMatcher.matchingAll()– を呼び出したが、前の例で使用した ExampleMatcher.matching()と同じ動作をしていることに注意してください。

6.3. カスタムマッチング

また、プロパティごとにマッチャーの動作を調整し、 ExampleMatcher.matchingAny()を使用して任意のプロパティを照合することもできます。

@Test
public void givenPassengers_whenFindByExampleCustomMatcher_thenExpectedReturned() {
    Passenger jill = Passenger.from("Jill", "Smith", 50);
    Passenger eve = Passenger.from("Eve", "Jackson", 95);
    Passenger fred = Passenger.from("Fred", "Bloggs", 22);
    Passenger siya = Passenger.from("Siya", "Kolisi", 85);
    Passenger ricki = Passenger.from("Ricki", "Bobbie", 36);

    ExampleMatcher customExampleMatcher = ExampleMatcher.matchingAny()
      .withMatcher("firstName", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
      .withMatcher("lastName", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase());

    Example<Passenger> example = Example.of(Passenger.from("e", "s", null), customExampleMatcher);

    List<Passenger> passengers = repository.findAll(example);

    assertThat(passengers, contains(jill, eve, fred, siya));
    assertThat(passengers, not(contains(ricki)));
}

6.4. プロパティを無視する

一方、プロパティのサブセットに対してのみクエリを実行したい場合もあります。

これは、 ExampleMatcher.ignorePaths(String…paths)を使用して一部のプロパティを無視することで実現します。

@Test
public void givenPassengers_whenFindByIgnoringMatcher_thenExpectedReturned() {
    Passenger jill = Passenger.from("Jill", "Smith", 50); 
    Passenger eve = Passenger.from("Eve", "Jackson", 95); 
    Passenger fred = Passenger.from("Fred", "Bloggs", 22);
    Passenger siya = Passenger.from("Siya", "Kolisi", 85);
    Passenger ricki = Passenger.from("Ricki", "Bobbie", 36);

    ExampleMatcher ignoringExampleMatcher = ExampleMatcher.matchingAny()
      .withMatcher("lastName", ExampleMatcher.GenericPropertyMatchers.startsWith().ignoreCase())
      .withIgnorePaths("firstName", "seatNumber");

    Example<Passenger> example = Example.of(Passenger.from(null, "b", null), ignoringExampleMatcher);

    List<Passenger> passengers = repository.findAll(example);

    assertThat(passengers, contains(fred, ricki));
    assertThat(passengers, not(contains(jill));
    assertThat(passengers, not(contains(eve)); 
    assertThat(passengers, not(contains(siya)); 
}

7. 結論

この記事では、Query byExampleAPIの使用方法を示しました。

ExampleおよびExampleMatcherQueryByExampleExecutorインターフェースを使用して、サンプルデータインスタンスを使用してテーブルをクエリする方法を示しました。

結論として、コードはGitHubにあります。