Spring Data JPA 最为优秀的特性就是可以通过自定义方法名称生成查询来轻松创建查询 SQL.Spring Data JPA 提供了一个 Repository 编程模型, 最简单的方式就是通过扩展 JpaRepository, 我们获得了一堆通用的 CRUD 方法, 例如 save,findAll,delete 等. 并且使用这些关键字可以构建很多的数据库单表查询接口:
- public interface CustomerRepository extends JpaRepository<Customer, Long> {
- Customer findByEmailAddress(String emailAddress);
- List<Customer> findByLastname(String lastname, Sort sort);
- Page<Customer> findByFirstname(String firstname, Pageable pageable);
- }
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 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();
第一行 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) ->{
- return cb.equal(root.get(Customer_.birthday), today);
- };
- }
- // 查询条件: 客户创建日期在两年以前
- public static Specification<Customer> isLongTermCustomer() {
- return (root, query, cb) ->{
- return cb.lessThan(root.get(Customer_.createdAt), new LocalDate.minusYears(2));
- };
- }
- }
现在可以通过 CustomerRepository 执行以下操作:
- customerRepository.findAll(hasBirthday());
- customerRepository.findAll(isLongTermCustomer());
我们创建了可以单独执行的可重用谓词查询条件, 我们可以结合使用这些单独的谓词来满足我们的业务需求. 我们可以使用 and(...) 和 or(...)连接 specification.
customerRepository.findAll(where(customerHasBirthday()).and(isLongTermCustomer()));
与使用 JPA Criteria API 相比, 它读起来很流利, 提高了可读性并提供了更多的灵活性.
期待您的关注
向您推荐博主的系列文档:《手摸手教您学习 SpringBoot 系列 - 16 章 97 节》 http://springboot.zimug.com/
来源: https://www.cnblogs.com/zimug/p/12016271.html