规格
JPA 2引入了一个条件API,可以用来编程构建查询。通过写标准你为域类定义查询的where子句。再退一步,这些准则可以被视为由JPA标准API约束描述的实体上的谓词。
Spring Data JPA 借鉴了 Eric Evans 著作《领域驱动设计》中的规范概念,遵循相同的语义,并提供了 JPA 标准 API 来定义此类规范的 API。为了支持规范,你可以通过JpaSpecificationExecutor界面,具体如下:
public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
…
}
附加界面有方法允许你以多种方式运行规格。例如,findAll(查找所有方法返回所有符合规范的实体,如下示例所示:
List<T> findAll(Specification<T> spec);
这规范接口定义如下:
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
CriteriaBuilder builder);
}
规范可以轻松地在实体之上构建一套可扩展的谓词,然后将其组合并使用JpaRepository无需为每一个需要的组合声明查询(方法),如下例所示:
public class CustomerSpecs {
public static Specification<Customer> isLongTermCustomer() {
return (root, query, builder) -> {
LocalDate date = LocalDate.now().minusYears(2);
return builder.lessThan(root.get(Customer_.createdAt), date);
};
}
public static Specification<Customer> hasSalesOfMoreThan(MonetaryAmount value) {
return (root, query, builder) -> {
// build query here
};
}
}
这客户_类型是使用JPA元模型生成器生成的元模型类型(示例见Hibernate实现文档)。
所以表达式,Customer_.createdAt,假设客户端有创造了属性 类型日期.
除此之外,我们还在业务需求抽象层面表达了一些标准,并创建了可执行文件规格.
所以客户端可能会使用规范如下:
List<Customer> customers = customerRepository.findAll(isLongTermCustomer());
为什么不为这种数据访问创建一个查询呢?使用单一规范相比普通查询声明,优势不大。当你把规范结合起来创造出新的产品时,规格的力量才真正闪耀规范对象。你可以通过以下默认方法实现:规范我们提供类似以下表达的服务:
MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
isLongTermCustomer().or(hasSalesOfMoreThan(amount)));
规范提供一些“胶合代码”默认方法来串联和组合规范实例。这些方法允许你通过创建新的数据访问层来扩展数据访问层规范实现并与已有实现结合。
而在 JPA 2.1 中,标准构建器引入了API。CriteriaDelete.该服务通过以下方式提供JpaSpecificationExecutor 的 'delete(Specification)应用程序接口。
规范删除条目。Specification<User> ageLessThan18 = (root, query, cb) -> cb.lessThan(root.get("age").as(Integer.class), 18)
userRepository.delete(ageLessThan18);
这规范构建一个条件,使得年龄域(表为整数)小于18.
传给用户仓库,它将使用 JPACriteriaDelete特征生成右删除操作。
然后返回已删除的实体数量。