正确使用Spring Data JPA规范

在优锐课的学习分享中探讨了关于,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相比,它读起来很流利,提高了可读性并提供了更多的灵活性。 这里唯一需要说明的是,提出规范实现需要相当多的编码工作。

以下是规范的一些优点:

  1. 所有“基本”查询都已实现,即findById,保存和删除
  2. 分页功能开箱即用。 您可以简单地将一个可分页对象从Controller传递到Service到您的存储库,并且可以正常工作(甚至可以排序)!
  3. 使用Spring的Specification API比普通的JPA简单一些,因为您只需创建谓词,而不必弄乱EntityManager和PersistenceContext。

如果您有任何要添加或共享的内容,请在下面的评论部分中留言。

祝大家学习愉快!

猜你喜欢

转载自www.cnblogs.com/youruike1/p/12053692.html