JPA Specification
Specification算是JPA里面比较灵活的查询规范了,方便实现复杂的查询方式。
为什么需要Specification
Spring-Data JPA 本身支持了比较简单的查询方式,也就是根据属性名成结合一些规范编写查询方法,例如,一个Customer对象有name属性,那么如果想要实现根据name来查询,只需要在接口文件中添加一个方法findByName(String name)即可实现。
public interface CustomerRepository extends JpaRepository<Customer, Long> {
Customer findByName(String name);
Customer findByEmailAddress(String emailAddress);
List<Customer> findByLastname(String lastname, Sort sort);
Page<Customer> findByFirstname(String firstname, Pageable pageable);
}
但是在许多情况下,会有比较复杂的查询,那么这个时候通过自动生成查询方法的方式就不再可行。
应用场景
为了实现复杂查询,JPA提供了Criteria接口,这个是一套标准接口,来看一个例子,在一个平台中,当一个老客户(注册以来两年)生日的时候,系统想要发送一个优惠券给该用户,那么传统使用 JPA 2.0 Criteria API 去实现:
LocalDate today = new LocalDate();
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Customer> query = builder.createQuery(Customer.class);
Root<Customer> root = query.from(Customer.class);
Predicate hasBirthday = builder.equal(root.get(Customer_.birthday), today);
Predicate isLongTermCustomer = builder.lessThan(root.get(Customer_.createdAt), today.minusYears(2);
query.where(builder.and(hasBirthday, isLongTermCustomer));
em.createQuery(query.select(root)).getResultList();
- 首先获得时间,去比较用户的注册时间
- 接下来是获得JPA中查询使用的实例
- 设置查询条件,首先判断今天是否为某个客户的生日,然后判断是否为老客户
- 执行查询条件,获得满足条件的用户
这里面的主要问题就是代码扩展性比较差,因为需要设置CriteriaBuilder, CriteriaQuery, Root,同时这部分的代码可读性比较差。
JPA Specification实现复杂查询
Specification为了实现可重用的断言,JPA 里面引入了一个Specification接口,接口的封装很简单,如下
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb);
}
在Java8中,我们可以非常方便地实现如上使用Criteria实现的效果
public CustomerSpecifications {
public static Specification<Customer> customerHasBirthday() {
return (root, query, cb) -> {
return cb.equal(root.get(Customer_.birthday), today);
};
}