Spring Data JPA进阶——Specifications和Querydsl

本篇介绍一下spring Data JPA中能为数据访问程序的开发带来更多便利的特性,我们知道,Spring Data repository的配置很简单,一个典型的repository像下面这样:

  1. public interface CustomerRepository extends JpaRepository<Customer, Long> {
  2. Customer findByEmailAddress(String emailAddress);
  3. List<Customer> findByLastname(String lastname, Sort sort);
  4. Page<Customer> findByFirstname(String firstname, Pageable pageable);
  5. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

第一个方法表示根据email查询一个Customer,第二个方法表示根据lastName和排序条件查询一个Customer的集合,第三个方法表示根据fristName和分页的信息查询一页Customer

这样的方式非常简单,甚至不用编写方法的实现就可以实现查询的功能,但是这仍然有个弊端,如果查询条件增长,方法会越来越多,如果能动态的组装查询条件就好了

那么,可以吗?答案当然是yes

我们都知道JPA提供了Criteria API,下面我们就用一个例子,展示一下Criteria的使用,想象这样一个场景,我们想针对长期客户,在生日那天给他发一段祝福,我们怎么做呢?

使用Criteria API

我们有两个条件,生日和长期客户,我们假设两年前注册的就是长期客户吧,怎么用JPA 2.0的Criteria API实现呢:

  1. LocalDate today = new LocalDate();
  2. CriteriaBuilder builder = em.getCriteriaBuilder();
  3. CriteriaQuery<Customer> query = builder.createQuery(Customer.class);
  4. Root<Customer> root = query.from(Customer.class);
  5. Predicate hasBirthday = builder.equal(root.get(Customer_.birthday), today);
  6. Predicate isLongTermCustomer = builder.lessThan(root.get(Customer_.createdAt), today.minusYears(2);
  7. query.where(builder.and(hasBirthday, isLongTermCustomer));
  8. em.createQuery(query.select(root)).getResultList();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

我们先创建了一个LocalDate对象,然后是三行样板代码啊,后面两行是建立查询条件,然后通过where子句连在一起,然后执行查询

上面查询有两个问题

  • 第一,由于每次要先建立CriteriaBuilder,CriteriaQuery,Root,所以导致查询条件的重用和扩展性不是很好
  • 第二,上面程序可读性一般,并不能一目了然知道程序在干嘛

使用Specifications

为了重用查询条件,我们引入了Specification接口,这是从Eric Evans’ Domain Driven Design 一书中的概念衍生出来的,它为对一个实体查询的谓词定义了一个规范,实体类型由Specification接口的泛型参数来决定,这个接口只包含下面一个方法:

  1. public interface Specification<T> {
  2. Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb);
  3. }
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

我们现在可以通过一个工具类很容易的使用它:

  1. public CustomerSpecifications {
  2. public static Specification<Customer> customerHasBirthday() {
  3. return new Specification<Customer> {
  4. public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) {
  5. return cb.equal(root.get(Customer_.birthday), today);
  6. }
  7. };
  8. }
  9. public static Specification<Customer> isLongTermCustomer() {
  10. return new Specification<Customer> {
  11. public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) {
  12. return cb.lessThan(root.get(Customer_.createdAt), new LocalDate.minusYears(2));
  13. }
  14. };
  15. }
  16. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

诚然,这并不是最优雅的代码,但是至少很好地解决了我们重用判定条件的需求,如何执行呢,很简单,我们只要让repository继承JpaSpecificationExecutor接口即可:

  1. public interface CustomerRepository extends JpaRepository<Customer>, JpaSpecificationExecutor {
  2. // Your query methods here
  3. }
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

然后可以像下面这样调用:

  1. customerRepository.findAll(hasBirthday());
  2. customerRepository.findAll(isLongTermCustomer());
  • 1
  • 2
  • 1
  • 2

默认实现会为你提供CriteriaQuery,Root,CriteriaBuilder等对象,通过给定的Specification应用判定条件,然后执行查询,这样的好处就是我们可以随意组合查询条件,而不用写很多个方法,Specifications工具类提供了一写遍历方法来组合条件,例如and(…)、or(…)等连接方法,还有where(…)提供了更易读的表达形式,下面我们看一下效果:

  1. customerRepository.findAll(where(customerHasBirthday()).and(isLongTermCustomer()));
  • 1
  • 1

相比JPA Criteria API的原生接口,我们的实现更加具有扩展性和可读性,当时实现Specification的时候需要一点小波折,但这是值得的

使用Querydsl

为了解决上述的痛苦,一个叫Querydsl的开源项目也提供了类似的解决方案,但是实现有所不同,提供了更有好的API,而且不仅支持JPA,还支持hibernate,JDO,Lucene,JDBC甚至是原始集合的查询

为了使用Querydsl,需要在pom.xml中引入依赖并且配置一个额外的APT插件

  1. <plugin>
  2. <groupId>com.mysema.maven</groupId>
  3. <artifactId>maven-apt-plugin</artifactId>
  4. <version>1.0</version>
  5. <executions>
  6. <execution>
  7. <phase>generate-sources</phase>
  8. <goals>
  9. <goal>process</goal>
  10. </goals>
  11. <configuration>
  12. <outputDirectory>target/generated-sources</outputDirectory>
  13. <processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
  14. </configuration>
  15. </execution>
  16. </executions>
  17. </plugin>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

下面就可以通过QCustomer来实现我们上述的功能了

  1. QCustomer customer = QCustomer.customer;
  2. LocalDate today = new LocalDate();
  3. BooleanExpression customerHasBirthday = customer.birthday.eq(today);
  4. BooleanExpression isLongTermCustomer = customer.createdAt.lt(today.minusYears(2));
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

上面的写法不仅读来很顺畅,BooleanExpressions还可以直接重用,免去使用更多包装方法的写法,更酷的是还可以得到IDE代码自动完成的支持,要执行查询,跟Specification类似,让repository继承QueryDslPredicateExecutor接口即可:

  1. public interface CustomerRepository extends JpaRepository<Customer>, QueryDslPredicateExecutor {
  2. // Your query methods here
  3. }
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

可以通过下面的方式调用

  1. BooleanExpression customerHasBirthday = customer.birthday.eq(today);
  2. BooleanExpression isLongTermCustomer = customer.createdAt.lt(today.minusYears(2));
  3. customerRepository.findAll(customerHasBirthday.and(isLongTermCustomer));
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

总结

Spring Data JPA repository抽象允许通过把JPA Criteria API包装到Specification中来简化开发,还可以使用Querydsl,实现方法也很简单,分别集成JpaSpecificationExecutor或者QueryDslPredicateExecutor即可,当然,如果需要的话,一起使用也没问题

原文https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/

Spring Data JPA进阶——Specifications和Querydsl的更多相关文章

  1. Spring Data JPA 的 Specifications动态查询

    主要的结构: 有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询. ...

  2. Spring Data JPA 进阶

    Java持久化查询语言概述 Java持久化查询语言(JPQL)是一种可移植的查询语言,旨在以面向对象表达式语言的表达式,将SQL语法和简单查询语义绑定在一起,使用这种语言编写的查询是可移植的,可以被编 ...

  3. 一文搞定 Spring Data JPA

    Spring Data JPA 是在 JPA 规范的基础上进行进一步封装的产物,和之前的 JDBC.slf4j 这些一样,只定义了一系列的接口.具体在使用的过程中,一般接入的是 Hibernate 的 ...

  4. 深入探索Spring Data JPA, 从Repository 到 Specifications 和 Querydsl

    数据访问层,所谓的CRUD是后端程序员的必修课程,Spring Data JPA 可以让我们来简化CRUD过程,本文由简入深,从JPA的基本用法,到各种高级用法. Repository Spring ...

  5. Spring Data JPA教程, 第五部分: Querydsl(未翻译)

    The fourth part of my Spring Data JPA tutorialdescribed how you can implement more advanced queries ...

  6. 如何在Spring Data JPA中引入Querydsl

    一.环境说明 基础框架采用Spring Boot.Spring Data JPA.Hibernate.在动态查询中,有一种方式是采用Querydsl的方式. 二.具体配置 1.在pom.xml中,引入 ...

  7. Spring Data JPA系列3:JPA项目中核心场景与进阶用法介绍

    大家好,又见面了. 到这里呢,已经是本SpringData JPA系列文档的第三篇了,先来回顾下前面两篇: 在第1篇<Spring Data JPA系列1:JDBC.ORM.JPA.Spring ...

  8. Spring data JPA中使用Specifications动态构建查询

    有时我们在查询某个实体的时候,给定的条件是不固定的,这是我们就需要动态 构建相应的查询语句,在JPA2.0中我们可以通过Criteria接口查询,JPA criteria查询.相比JPQL,其优势是类 ...

  9. Spring Data JPA教程, 第四部分: JPA Criteria Queries(未翻译)

    The third part of my Spring Data JPA tutorialdescribed how you can create custom queries by using qu ...

随机推荐

  1. AJAX笔试面试题汇总

    AJAX笔试面试题汇总 Javascript 1. 什么是ajax,为什么要使用Ajax(请谈一下你对Ajax的认识) 什么是ajax: AJAX是“Asynchronous JavaScript a ...

  2. PHP file_get_contents设置超时处理方法

    从PHP5开始,file_get_content已经支持context了(手册上写着:5.0.0 Added the context support. ),也就是说,从5.0开始,file_get_c ...

  3. winform只能有一个实例运行且打开已运行窗口

    static class Program { private static Mutex onlyOne; [STAThread] static void Main() { onlyOne = new ...

  4. Senparc.Weixin.MP.Sample 配置redis服务器密码

    redis.windows-service.conf中加  requirepass 你的密码 <!-- Cache.Redis连接配置 --> <add key="Cach ...

  5. Orchard源码分析(4.1):Orchard.Environment.CollectionOrderModule类

    CollectionOrderModule类是一个Autofac模块(Module,将一系列组件和相关的功能包装在一起),而非Orchard模块.其作用是保证多个注册到容器的组件能按FIFO(Firs ...

  6. C# 对象 序列化 XML

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.I ...

  7. vim配色方案设置(更换vim配色方案)

    vim配色后,我的 设定底色为黑色,字体为绿色,然后将文件夹设为洋红,默认的注释换为淡黄:其实有一种简单的方法,就是设定为系统配置好的配色方案:转载文章如下:   ---------------- ( ...

  8. 如何在JDK1.8中愉快地处理日期和时间

    如何在JDK1.8中愉快地处理日期和时间 JDK1.8新增了LocalDate和LocalTime接口,为什么要搞一套全新的处理日期和时间的API?因为旧的java.util.Date实在是太难用了. ...

  9. php 遍历目录下的所以文件和文件夹

    <?php/** * 遍历文件夹和文件列 * @author lizhiming * @date 2016/06/30 */define('DS', DIRECTORY_SEPARATOR); ...

  10. HighCharts日期及数值格式化

    1.函数原型   1 dateFormat(Stringformat,[Numbertime],[Booleancapitalize])::String 2.说明 格式化JavaScript 时间(也 ...