Specifications

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

JPA 2 introduces a criteria API that you can use to build queries programmatically. By writing a criteria, you define the where clause of a query for a domain class. Taking another step back, these criteria can be regarded as a predicate over the entity that is described by the JPA criteria API constraints.

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

Spring Data JPA takes the concept of a specification from Eric Evans' book, “Domain Driven Design”, following the same semantics and providing an API to define such specifications with the JPA criteria API. To support specifications, you can extend your repository interface with the JpaSpecificationExecutor interface, as follows:

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

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

The additional interface has methods that let you run specifications in a variety of ways. For example, the findAll method returns all entities that match the specification, as shown in the following example:

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

Specification 接口定义如下:

The Specification interface is defined as follows:

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

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

Specifications can easily be used to build an extensible set of predicates on top of an entity that then can be combined and used with JpaRepository without the need to declare a query (method) for every needed combination, as shown in the following example:

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

The Customer_ type is a metamodel type generated using the JPA Metamodel generator (see the Hibernate implementation’s documentation for an example). So the expression, Customer_.createdAt, assumes the Customer has a createdAt attribute of type Date. Besides that, we have expressed some criteria on a business requirement abstraction level and created executable Specifications. So a client might use a Specification as follows:

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

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

Why not create a query for this kind of data access? Using a single Specification does not gain a lot of benefit over a plain query declaration. The power of specifications really shines when you combine them to create new Specification objects. You can achieve this through the default methods of Specification we provide to build expressions similar to the following:

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

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

Specification offers some “glue-code” default methods to chain and combine Specification instances. These methods let you extend your data access layer by creating new Specification implementations and combining them with already existing implementations.

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

And with JPA 2.1, the CriteriaBuilder API introduced CriteriaDelete. This is provided through JpaSpecificationExecutor’s `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 操作。然后,它将返回已删除的实体数量。

The Specification builds up a criteria where the age field (cast as an integer) is less than 18. Passed on to the userRepository, it will use JPA’s CriteriaDelete feature to generate the right DELETE operation. It then returns the number of entities deleted.