Spring Data Query by Example API按示例查询教程 | Baeldung


在本教程中,我们将学习如何使用Spring Data Query by Example API的Spring Data Query查询数据 。注意,这是一个很长的专有名词:Spring Data Query by Example API,Spring data按示例查询。

首先,我们将定义要查询的数据的模式。接下来,我们将研究Spring Data中的一些相关类。然后,我们将通过几个例子。

让我们开始吧!
我们的测试数据是乘客姓名列表以及他们占用的座位。

Jill    Smith            50
Eve    Jackson            94
Fred    Bloggs            22
Ricki    Bobbie            36
Siya    Kolisi            85

让我们创建我们需要的Spring Data Repository并提供我们的域类和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.
}

首先,我们来看看 Spring Data的 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的方法。
重要的是要意识到probe是我们实体的实例:

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());
    }
}

总之,probe和ExampleMatcher一起指定了我们的查询。

限制:
与所有事情一样,Query by Example API也有一些限制。例如:

  • 不支持嵌套和分组语句,例如:  ( firstName =?0和 lastName =?1)或seatNumber =?2
  • 字符串匹配仅支持精确的,不区分大小写的,开始,结束,包含和正则表达式
  • String以外的所有类型都只是精确匹配

现在我们对API及其局限性稍微熟悉一下,让我们深入研究一些例子。

区分大小写
让我们从一个简单的例子开始,讨论默认行为:

@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所有非空的属性来进行。因此,匹配在字符串属性上区分大小写。
但是,如果我们所能做的就是对所有非空属性进行精确匹配,那就不会太有用了。
这就是ExampleMatcher的用武之地。通过构建我们自己的  ExampleMatcher,我们可以自定义行为以满足我们的需求。

不区分大小写
考虑到这一点,让我们看看另一个例子,这次使用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()具有相同的行为  。


自定义匹配
我们还可以基于每个属性调整匹配器的行为,并使用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)));
}

忽略属性
另一方面,我们也可能只想查询属性的子集。
我们通过使用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)); 
}

结论
在本文中,我们演示了如何使用Query by Example API。
我们已经演示了如何使用  Example  和ExampleMatcher  以及QueryByExampleExecutor  接口来使用示例数据实例查询表。
总之,您可以在GitHub上找到代码。