|
此版本仍在开发中,尚未被视为稳定版。如需使用最新的稳定版本,请访问 Spring Data JPA 4.0.4! |
规范
JPA 的 Criteria API 允许你以编程方式构建查询。
Spring Data JPA 的 Specification 提供了一个小巧而专注的 API,用于表达针对实体的谓词,并可在多个仓库之间复用。
该设计基于 Eric Evans 在《领域驱动设计》一书中提出的“规约”(specification)概念,遵循相同的语义,提供了一个使用 JPA 定义查询条件的 API。
为了支持规约(Specifications),你可以让你的仓库接口继承 JpaSpecificationExecutor 接口,如下所示:
public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
}
规范(Specification)是使用 Criteria API 表达的对实体的一种谓词。 Spring Data JPA 提供了两个入口点:
-
PredicateSpecification: Spring Data JPA 4.0 引入的灵活且与查询类型无关的接口。 -
Specification(以及UpdateSpecification、DeleteSpecification):查询绑定变体。
谓词规范
PredicateSpecification 接口以最少的依赖项进行定义,从而支持广泛的功能组合:
public interface PredicateSpecification<T> {
Predicate toPredicate(From<?, T> from, CriteriaBuilder builder);
}
规格(Specifications)可以轻松用于构建一组可扩展的谓词,并与 JpaRepository 一起使用,从而无需为每种所需的组合都声明一个查询(方法),如下例所示:
class CustomerSpecs {
static PredicateSpecification<Customer> isLongTermCustomer() {
return (from, builder) -> {
LocalDate date = LocalDate.now().minusYears(2);
return builder.lessThan(from.get(Customer_.createdAt), date);
};
}
static PredicateSpecification<Customer> hasSalesOfMoreThan(MonetaryAmount value) {
return (from, builder) -> {
// build predicate for sales > value
};
}
}
Customer_ 类型是使用 JPA 元模型生成器生成的元模型类型(参见Hibernate 实现文档中的示例)。
因此,表达式 Customer_.createdAt 假定 Customer 具有一个类型为 createdAt 的 Date 属性。
除此之外,我们还在业务需求的抽象层面上表达了一些条件,并创建了可执行的 Specifications。
直接在仓库中使用一个规格(Specification):
List<Customer> customers = customerRepository.findAll(isLongTermCustomer());
规格在组合使用时最为有价值:
MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
isLongTermCustomer().or(hasSalesOfMoreThan(amount))
);
Specification, UpdateSpecification, DeleteSpecification
Specification 接口已存在更长时间,并且根据 Criteria API 的限制,它与特定查询类型(select、update、delete)相关联。
三个规范接口定义如下:
-
Specification
-
UpdateSpecification
-
DeleteSpecification
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
CriteriaBuilder builder);
}
public interface UpdateSpecification<T> {
Predicate toPredicate(Root<T> root, CriteriaUpdate<T> update,
CriteriaBuilder builder);
}
public interface DeleteSpecification<T> {
Predicate toPredicate(Root<T> root, CriteriaDelete<T> delete,
CriteriaBuilder builder);
}
Specification 对象可以直接构造,也可以通过复用 PredicateSpecification 实例来构造,如下例所示:
public class CustomerSpecs {
public static UpdateSpecification<Customer> updateLastnameByFirstnameAndLastname(String newLastName, String currentFirstname, String currentLastname) {
return UpdateSpecification<User> updateLastname = UpdateSpecification.<User> update((root, update, criteriaBuilder) -> {
update.set("lastname", newLastName);
}).where(hasFirstname(currentFirstname).and(hasLastname(currentLastname)));
}
public static PredicateSpecification<Customer> hasFirstname(String firstname) {
return (root, builder) -> {
return builder.equal(from.get("firstname"), value);
};
}
public static PredicateSpecification<Customer> hasLastname(String lastname) {
return (root, builder) -> {
// build query here
};
}
}
流畅 API
JpaSpecificationExecutor 定义了流畅的查询方法,用于基于 Specification 实例灵活地执行查询:
-
对于
PredicateSpecification:findBy(PredicateSpecification<T> spec, Function<? super SpecificationFluentQuery<S>, R> queryFunction) -
对于
Specification:findBy(Specification<T> spec, Function<? super SpecificationFluentQuery<S>, R> queryFunction)
与其他方法一样,它会执行一个从 Specification 派生的查询。
然而,query 方法允许你控制那些通常无法动态控制的查询执行细节。
你可以通过调用 SpecificationFluentQuery 的各种中间方法和终止方法来实现这一点。
中间方法
-
sortBy:为您的结果应用排序。 重复调用该方法会依次追加每个Sort(注意,使用已排序的page(Pageable)调用Pageable会覆盖之前的所有排序顺序)。 -
limit:限制结果数量。 -
as:指定要读取或投影到的类型。 -
project:限制查询的属性。
终结方法
-
first、firstValue:返回第一个值。first返回一个Optional<T>,如果查询未产生任何结果,则返回Optional.empty()。firstValue是其可空变体,无需使用Optional。 -
one、oneValue:返回单个值。one返回一个Optional<T>,如果查询未产生任何结果,则返回Optional.empty()。oneValue是其可为空的变体,无需使用Optional。 如果找到多个匹配项,则抛出IncorrectResultSizeDataAccessException。 -
all:将所有结果以List<T>的形式返回。 -
page(Pageable):将所有结果以Page<T>的形式返回。 -
slice(Pageable):将所有结果以Slice<T>的形式返回。 -
scroll(ScrollPosition):使用滚动(偏移量、键集)以Window<T>的形式检索结果。 -
stream():返回一个Stream<T>以惰性方式处理结果。 该流是有状态的,使用后必须关闭。 -
count和exists:返回匹配实体的数量,或判断是否存在任何匹配项。
| 中间方法和终端方法必须在查询函数内调用。 |
Page 排序的投影 lastnamePage<CustomerProjection> page = repository.findBy(spec,
q -> q.as(CustomerProjection.class)
.page(PageRequest.of(0, 20, Sort.by("lastname")))
);
lastname 排序后可能存在的多个结果中的最后一个Optional<Customer> match = repository.findBy(spec,
q -> q.sortBy(Sort.by("lastname").descending())
.first()
);