使用 Spring Data JPA 的 Specification 构建数据库查询
}
findByEmailAddress 生成的 SQL 是根据 email_address 字段查询 Customer 表的数据
findByLastname 根据 lastname 字段查询 Customer 表的数据
findByFirstname 根据 firstname 字段查询 Customer 表的数据
以上所有的查询都不用我们手写 SQL,查询生成器自动帮我们工作,对于开发人员来说只需要记住一些关键字,如:findBy、delete 等等。但是,有时我们需要创建复杂一点的查询,就无法利用查询生成器。可以使用本节介绍的 Specification 来完成。
笔者还是更愿意手写 SQL 来完成复杂查询,但是有的时候偶尔使用一下 Specification 来完成任务,也还是深得我心。不排斥、不盲从。没有最好的方法,只有最合适的方法!
一、使用 Criteria API 构建复杂的查询
是的,除了 specification,我们还可以使用 Criteria API 构建复杂的查询,但是没有 specification 好用。我们来看一下需求:在客户生日当天,我们希望向所有长期客户(2 年以上)发送优惠券。我们如何该检索 Customer?
我们有两个谓词查询条件:
生日
长期客户-2 年以上的客户。
下面是使用 JPA 2.0 Criteria API 的实现方式:
LocalDate toda
y = 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();
第一行
LocalDate
用于比较客户的生日和今天的日期。em 是 javax.persistence.EntityManager下三行包含用于查询 Customer 实体的 JPA 基础结构实例的样板代码。
然后,在接下来的两行中,我们将构建谓词查询条件
在最后两行中,where 用于连接两个谓词查询条件,最后一个用于执行查询。
此代码的主要问题在于,谓词查询条件不易于重用,您需要先设置?CriteriaBuilder, CriteriaQuery
,和Root
。另外,代码的可读性也很差。
二、specification
为了能够定义可重用谓词条件,我们引入了 Specification 接口。
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb);
}
结合 Java 8 的 lambda 表达式使用 Specification 接口时,代码变得非常简单
public CustomerSpecifications {
//查询条件:生日为今天
public static Specification<Customer> customerHasBirthday() {
return (root, query, cb) ->{
评论