在优锐课的学习分享中探讨了关于,Spring Data JPA的创建主要是为了通过按方法名称生成查询来轻松创建查询。 但是,有时我们需要创建复杂的查询,而无法利用查询生成器。码了很多知识笔记分享给大家。
Spring Data JPA提供了一个存储库编程模型,该模型以每个受管域对象的接口开头。 定义这些接口有两个目的:首先,通过扩展JpaRepository,我们获得了一堆通用的CRUD方法,例如save,findAll,delete等。 其次,这将允许Spring Data JPA存储库基础结构扫描该接口的类路径并为其创建Spring Bean。 典型的存储库界面如下所示:
1 public interface CustomerRepository extends JpaRepository<Customer, Long> { 2 3 Customer findByEmailAddress(String emailAddress); 4 5 List<Customer> findByLastname(String lastname, Sort sort); 6 7 Page<Customer> findByFirstname(String firstname, Pageable pageable); 8 9 } 10 11
要创建复杂的查询,为什么要指定规格?
是的,可以使用Criteria API构建复杂的查询。 要了解为什么要使用规范,我们考虑一个简单的业务需求。 我们将使用Criteria API以及随后的规范来实现此要求。
这是用例:在客户生日那天,我们希望向所有长期客户发送优惠券。 我们如何检索一个匹配的?
我们有两个谓词:
1 LocalDate today = new LocalDate(); 2 3 CriteriaBuilder builder = em.getCriteriaBuilder(); 4 5 CriteriaQuery<Customer> query = builder.createQuery(Customer.class); 6 7 Root<Customer> root = query.from(Customer.class); 8 9 Predicate hasBirthday = builder.equal(root.get(Customer_.birthday), today); 10 11 Predicate isLongTermCustomer = builder.lessThan(root.get(Customer_.createdAt), today.minusYears(2); 12 13 query.where(builder.and(hasBirthday, isLongTermCustomer)); 14 15 em.createQuery(query.select(root)).getResultList(); 16 17
在上面的代码中,
- ·第一行创建了LocalDate以比较客户的生日和今天的日期。
- ·以下三行包含用于设置必要的JPA基础结构实例的样板代码。
- ·然后,在接下来的两行中,我们将构建谓词
- ·在最后两行中,一个用于连接两个谓词,最后一个用于执行查询。
此代码的主要问题在于谓词不易于外部化和重用,因为您需要先设置CriteriaBuilder,CriteriaQuery和Root。 另外,由于难以快速推断出代码的意图,因此代码的可读性很差。
规格
为了能够定义可重用谓词,我们引入了规范接口,该接口源自Eric Evans的《域驱动设计》一书中引入的概念。 它将规范定义为实体的谓词,这正是规范接口所代表的含义。 实际上,这仅包含一个方法:
1 public interface Specification<T> { 2 3 Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb); 4 5 } 6 7
使用Java 8时,代码变得非常清晰易懂。
1 public CustomerSpecifications { 2 3 public static Specification<Customer> customerHasBirthday() { 4 5 return (root, query, cb) ->{ 6 7 return cb.equal(root.get(Customer_.birthday), today); 8 9 }; 10 11 } 12 13 public static Specification<Customer> isLongTermCustomer() { 14 15 return (root, query, cb) ->{ 16 17 return cb.lessThan(root.get(Customer_.createdAt), new LocalDate.minusYears(2)); 18 19 }; 20 21 } 22 23 } 24 25
客户端现在可以执行以下操作:
1 customerRepository.findAll(hasBirthday()); 2 3 customerRepository.findAll(isLongTermCustomer()); 4 5
在这里,基本实现将为您准备CriteriaQuery,Root和CriteriaBuilder,应用由给定规范创建的谓词并执行查询。
我们只是创建了可以单独执行的可重用谓词。 我们可以结合使用这些单独的谓词来满足我们的业务需求。 我们有一个帮助程序类规范,它提供了(和)和(或)方法来连接原子规范。
1 customerRepository.findAll(where(customerHasBirthday()).and(isLongTermCustomer()));
与仅使用JPA Criteria API相比,它读起来很流利,提高了可读性并提供了更多的灵活性。 这里唯一需要说明的是,提出规范实现需要相当多的编码工作。
以下是规范的一些优点:
- 所有“基本”查询都已实现,即findById,保存和删除
- 分页功能开箱即用。 您可以简单地将一个可分页对象从Controller传递到Service到您的存储库,并且可以正常工作(甚至可以排序)!
- 使用Spring的Specification API比普通的JPA简单一些,因为您只需创建谓词,而不必弄乱EntityManager和PersistenceContext。
如果您有任何要添加或共享的内容,请在下面的评论部分中留言。
祝大家学习愉快!