在本教程中,我们将学习如何使用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上找到代码。