JPA 2引入了一个标准API,您可以使用它以编程方式构建查询。通过编写条件,可以定义domain class查询的where子句。再后退一步,这些标准可以看作是JPA标准API约束所描述的实体的断言。
Spring Data JPA从Eric Evans的《领域驱动设计》一书中获得了规范的概念,遵循相同的语义,并提供了一个API来用JPA标准API定义这样的规范。为了支持规范,您可以使用JpaSpecificationExecutor接口扩展您的存储库接口,如下所示:
public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
…
}
附加的接口具有允许您以各种方式运行规范的方法:
public interface JpaSpecificationExecutor<T> {
Optional<T> findOne(@Nullable Specification<T> var1);
List<T> findAll(@Nullable Specification<T> var1);
Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
List<T> findAll(@Nullable Specification<T> var1, Sort var2);
long count(@Nullable Specification<T> var1);
}
public interface Specification<T> extends Serializable {
long serialVersionUID = 1L;
static <T> Specification<T> not(@Nullable Specification<T> spec) {
return spec == null ? (root, query, builder) -> {
return null;
} : (root, query, builder) -> {
return builder.not(spec.toPredicate(root, query, builder));
};
}
static <T> Specification<T> where(@Nullable Specification<T> spec) {
return spec == null ? (root, query, builder) -> {
return null;
} : spec;
}
default Specification<T> and(@Nullable Specification<T> other) {
return SpecificationComposition.composed(this, other, CriteriaBuilder::and);
}
default Specification<T> or(@Nullable Specification<T> other) {
return SpecificationComposition.composed(this, other, CriteriaBuilder::or);
}
@Nullable
Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
}
规范可以很容易地用于在实体之上构建可扩展的断言,然后可以与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
};
}
}
使用简单的规范:
List<Customer> customers = customerRepository.findAll(isLongTermCustomer());