Specifications

JPA 2 引入了你可用以编程方式构建查询的条件 API。通过编写 criteria,你可以为特定领域类的查询定义 where 子句。进一步后退一步,这些条件可被视为对由 JPA 条件 API 约束描述的实体的谓词。

Spring Data JPA 采用 Eric Evans 的书籍“领域驱动设计”中的规范概念,遵循相同的语义,并提供了一个 API 来使用 JPA 标准 API 定义此类规范。要支持规范,你可以使用 JpaSpecificationExecutor 接口扩展你的存储库接口,如下所示:

public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
 …
}

附加接口具有允许你以多种方式运行规范的方法。例如,findAll 方法返回与规范匹配的所有实体,如以下示例所示:

List<T> findAll(Specification<T> spec);

Specification 接口定义如下:

public interface Specification<T> {
  Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
            CriteriaBuilder builder);
}

规范可以轻松用于在实体之上构建可扩展的谓词集,然后可以结合使用 JpaRepository,而无需为每个所需的组合声明查询(方法),如以下示例所示:

Example 1. Specifications for a Customer
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
    };
  }
}

Customer_ 类型是使用 JPA 元模型生成器生成的元模型类型(请参阅 Hibernate implementation’s documentation for an example)。因此,表达式 Customer_.createdAt 假设 Customer 具有类型为 DatecreatedAt 属性。此外,我们在业务需求抽象层表达了一些标准并创建了可执行 Specifications。因此,客户端可能会按照如下方式使用 Specification

Example 2. Using a simple Specification
List<Customer> customers = customerRepository.findAll(isLongTermCustomer());

为什么不为这种数据访问创建查询?使用单个 Specification 不会比普通查询声明获得更多好处。规范的强大之处在于将其组合起来以创建新的 Specification 对象时体现出来。你可以通过我们提供的 Specification 的默认方法实现这一点,以构建类似于以下的表达式:

Example 3. Combined Specifications
MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
  isLongTermCustomer().or(hasSalesOfMoreThan(amount)));

Specification 提供了一些“胶合代码”默认方法来链接和组合 Specification 实例。这些方法允许你通过创建新的 Specification 实现并将其与已经存在的实现相结合来扩展你的数据访问层。

并且通过 JPA 2.1,CriteriaBuilder API 引入了 CriteriaDelete。这是通过 JpaSpecificationExecutor 的 `delete(Specification) API 提供的。

Example 4. Using a Specification to delete entries.
Specification<User> ageLessThan18 = (root, query, cb) -> cb.lessThan(root.get("age").as(Integer.class), 18)

userRepository.delete(ageLessThan18);

Specification 会构建一个标准,其中 age 字段(转换后的整数类型)小于 18。传递给 userRepository 后,它将使用 JPA 的 CriteriaDelete 特性来生成正确的 DELETE 操作。然后,它将返回已删除的实体数量。