对于最新的稳定版本,请使用 Spring Data Relational 4.0.4spring-doc.cadn.net.cn

按示例查询

介绍

本章介绍了按示例查询(Query by Example)并说明了如何使用它。spring-doc.cadn.net.cn

按示例查询(Query by Example,QBE)是一种用户友好的查询技术,具有简单的接口。 它支持动态创建查询,且无需编写包含字段名称的查询语句。 事实上,按示例查询完全不需要你使用特定数据存储的查询语言来编写查询。spring-doc.cadn.net.cn

本章介绍了按示例查询(Query by Example)的核心概念。 这些信息来自 Spring Data Commons 模块。 根据您所使用的数据库,字符串匹配支持可能会受到限制。

用法

按示例查询(Query by Example)API 由四个部分组成:spring-doc.cadn.net.cn

  • Probe:一个已填充字段的领域对象的实际示例。spring-doc.cadn.net.cn

  • ExampleMatcherExampleMatcher 包含如何匹配特定字段的详细信息。 它可以在多个 Example 之间重复使用。spring-doc.cadn.net.cn

  • Example:一个Example由探针(probe)和ExampleMatcher组成。 它用于创建查询。spring-doc.cadn.net.cn

  • FetchableFluentQuery:一个 FetchableFluentQuery 提供了流式 API,允许对从 Example 派生的查询进行进一步自定义。 使用该流式 API,您可以为查询指定排序、投影和结果处理方式。spring-doc.cadn.net.cn

按示例查询(Query by Example)非常适合以下几种使用场景:spring-doc.cadn.net.cn

按示例查询(Query by Example)也存在若干限制:spring-doc.cadn.net.cn

  • 不支持嵌套或分组的属性约束,例如 firstname = ?0 or (firstname = ?1 and lastname = ?2)spring-doc.cadn.net.cn

  • 不支持匹配集合或映射。spring-doc.cadn.net.cn

  • 针对特定存储的字符串匹配支持。 根据您使用的数据库,字符串匹配可以支持 starts(开头匹配)/contains(包含)/ends(结尾匹配)/regex(正则表达式)等操作。spring-doc.cadn.net.cn

  • 其他属性类型的精确匹配。spring-doc.cadn.net.cn

在开始使用按示例查询(Query by Example)之前,您需要有一个领域对象。 首先,为您的仓库创建一个接口,如下例所示:spring-doc.cadn.net.cn

示例 Person 对象
public class Person {

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

  // … getters and setters omitted
}

前面的示例展示了一个简单的领域对象。 您可以使用它来创建一个Example。 默认情况下,值为null的字段会被忽略,字符串则使用存储特定的默认方式匹配。spring-doc.cadn.net.cn

将属性包含到示例查询(Query by Example)条件中是基于可空性的。 使用原始类型的属性(intdouble、…)始终会被包含,除非 ExampleMatcher 忽略了该属性路径

示例可以通过使用 of 工厂方法或通过使用 ExampleMatcher 来构建。Example 是不可变的。 以下列表展示了一个简单的示例:spring-doc.cadn.net.cn

示例 1. 简单示例
Person person = new Person();                         (1)
person.setFirstname("Dave");                          (2)

Example<Person> example = Example.of(person);         (3)
1 创建域对象的新实例。
2 设置要查询的属性。
3 创建 Example

你可以通过使用仓库来运行示例查询。 为此,让你的仓库接口继承 QueryByExampleExecutor<T>。 以下代码片段展示了 QueryByExampleExecutor 接口的部分内容:spring-doc.cadn.net.cn

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 来指定自己的字符串匹配、空值处理以及针对特定属性的设置,如下例所示:spring-doc.cadn.net.cn

示例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 设置属性。
3 创建一个 ExampleMatcher,以期望所有值都匹配。 即使在此阶段不进行额外配置,它也是可用的。
4 构造一个新的 ExampleMatcher 以忽略 lastname 属性路径。
5 构造一个新的 ExampleMatcher,以忽略 lastname 属性路径并包含空值。
6 构造一个新的 ExampleMatcher,以忽略 lastname 属性路径,包含空值,并执行后缀字符串匹配。
7 基于领域对象和已配置的 Example 创建一个新的 ExampleMatcher

默认情况下,ExampleMatcher 要求探针(probe)上设置的所有值都必须匹配。 如果您希望获得与任意一个隐式定义的谓词相匹配的结果,请使用 ExampleMatcher.matchingAny()spring-doc.cadn.net.cn

您可以为单个属性(例如“firstname”和“lastname”,或者嵌套属性如“address.city”)指定行为。 您可以通过匹配选项和大小写敏感性对其进行调整,如下例所示:spring-doc.cadn.net.cn

配置匹配器选项
ExampleMatcher matcher = ExampleMatcher.matching()
  .withMatcher("firstname", endsWith())
  .withMatcher("lastname", startsWith().ignoreCase());
}

配置匹配器选项的另一种方式是使用 Lambda 表达式(Java 8 引入)。 这种方法会创建一个回调,要求实现者修改匹配器。 你无需返回匹配器,因为配置选项已保存在匹配器实例内部。 以下示例展示了一个使用 Lambda 表达式的匹配器:spring-doc.cadn.net.cn

使用 Lambda 表达式配置匹配器选项
ExampleMatcher matcher = ExampleMatcher.matching()
  .withMatcher("firstname", match -> match.endsWith())
  .withMatcher("firstname", match -> match.startsWith());
}

通过Example创建的查询使用配置的合并视图。 默认匹配设置可以在ExampleMatcher级别进行配置,而特定设置则可应用于具体的属性路径。 在ExampleMatcher上设置的配置会被属性路径设置所继承,除非这些属性路径明确地定义了自己的设置。 属性路径上的设置优先级高于默认设置。 下表描述了各种ExampleMatcher设置的作用范围:spring-doc.cadn.net.cn

表1. ExampleMatcher 设置的作用范围
设置 作用域

Null-handlingspring-doc.cadn.net.cn

ExampleMatcherspring-doc.cadn.net.cn

字符串匹配spring-doc.cadn.net.cn

ExampleMatcher 和属性路径spring-doc.cadn.net.cn

忽略属性spring-doc.cadn.net.cn

属性路径spring-doc.cadn.net.cn

大小写敏感性spring-doc.cadn.net.cn

ExampleMatcher 和属性路径spring-doc.cadn.net.cn

值转换spring-doc.cadn.net.cn

属性路径spring-doc.cadn.net.cn

流畅 API

QueryByExampleExecutor 通过 Example 定义了流畅的查询方法,用于基于 findBy(Example<S> example, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) 实例灵活地执行查询。spring-doc.cadn.net.cn

与其他方法一样,它会执行一个从 Example 派生的查询。 然而,query 方法允许你控制那些通常无法动态控制的查询执行细节。 你可以通过调用 FetchableFluentQuery 的各种中间方法和终止方法来实现这一点。spring-doc.cadn.net.cn

  • firstfirstValue:返回第一个值。first 返回一个 Optional<T>,如果查询未产生任何结果,则返回 Optional.empty()firstValue 是其可空变体,无需使用 Optionalspring-doc.cadn.net.cn

  • oneoneValue:返回单个值。one 返回一个 Optional<T>,如果查询未产生任何结果,则返回 Optional.empty()oneValue 是其可为空的变体,无需使用 Optional。 如果找到多个匹配项,则抛出 IncorrectResultSizeDataAccessExceptionspring-doc.cadn.net.cn

  • all:将所有结果以 List<T> 的形式返回。spring-doc.cadn.net.cn

  • page(Pageable):将所有结果以 Page<T> 的形式返回。spring-doc.cadn.net.cn

  • slice(Pageable):将所有结果以 Slice<T> 的形式返回。spring-doc.cadn.net.cn

  • scroll(ScrollPosition):使用滚动(偏移量、键集)以 Window<T> 的形式检索结果。spring-doc.cadn.net.cn

  • stream():返回一个 Stream<T> 以惰性方式处理结果。 该流是有状态的,使用后必须关闭。spring-doc.cadn.net.cn

  • countexists:返回匹配实体的数量,或判断是否存在任何匹配项。spring-doc.cadn.net.cn

中间方法和终端方法必须在查询函数内调用。
示例 3. 使用流式 API 获取一个按 Page 排序的投影 lastname
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()
);

这是一个示例:spring-doc.cadn.net.cn

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 查询单个项)。

这说明了如何使用一个领域对象来构建一个简单的探针(probe)。 在此例中,它将根据 Employee 对象的 name 字段等于 Frodo 进行查询。 null 字段将被忽略。spring-doc.cadn.net.cn

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()来匹配任意字段)
2 对于 name 字段,请使用一个通配符,该通配符将匹配字段的结尾部分
3 将列与 null 进行匹配(别忘了在关系型数据库中,NULL 并不等于 NULL)。
4 在构建查询时忽略 role 字段。
5 将自定义的 ExampleMatcher 插入到探针中。

也可以对任意属性应用 withTransform(),从而在构建查询之前对该属性进行转换。 例如,在创建查询之前,您可以对基于 toUpperCase() 的属性应用 String 方法。spring-doc.cadn.net.cn

当您事先不知道查询所需的所有字段时,Query By Example(按示例查询)真正展现出其优势。 如果您正在构建一个网页过滤器,允许用户选择字段,那么 Query By Example 是一种将这种灵活性高效地转化为查询的绝佳方式。spring-doc.cadn.net.cn