Spring Data JPA进阶——Specifications和Querydsl

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

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);
}
  • 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实现呢:

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();
  • 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接口的泛型参数来决定,这个接口只包含下面一个方法:

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

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

public CustomerSpecifications {

  public static Specification<Customer> customerHasBirthday() {
return new Specification<Customer> {
public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) {
return cb.equal(root.get(Customer_.birthday), today);
}
};
} public static Specification<Customer> isLongTermCustomer() {
return new Specification<Customer> {
public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) {
return cb.lessThan(root.get(Customer_.createdAt), new LocalDate.minusYears(2));
}
};
}
}
  • 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接口即可:

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

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

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

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

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

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

使用Querydsl

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

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

<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>maven-apt-plugin</artifactId>
<version>1.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources</outputDirectory>
<processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</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来实现我们上述的功能了

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

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

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

可以通过下面的方式调用

BooleanExpression customerHasBirthday = customer.birthday.eq(today);
BooleanExpression isLongTermCustomer = customer.createdAt.lt(today.minusYears(2));
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. OwinStartupAttribute

    尝试加载应用时出现了以下错误.- 找不到包含 OwinStartupAttribute 的程序集.- 找不到包含 Startup 或 [AssemblyName].Startup 类的程序集.若要禁用 ...

  2. ASP.NET WebAPI 08 Message,HttpConfiguration,DependencyResolver

    ASP.NET WebAPI 08 Message,HttpConfiguration,DependencyResolver   Message WebAPI作为通信架构必定包含包含请求与响应两个方法 ...

  3. java获取指定路径下的指定文件/java.io.File.listFiles(FilenameFilter filter)

    java.io.File.listFiles(FilenameFilter filter) 返回抽象路径名数组,表示在目录中此抽象路径名表示,满足指定过滤器的文件和目录. 声明 以下是java.io. ...

  4. Spring回调方法DisposableBean接口

    除了自定义的destroy-method.还可以实现DisposableBean接口,来回调bean销毁时候执行的方法,这个接口有一个destroy方法,生命周期是是destroy----bean销毁 ...

  5. c语言strtod()函数的用法

    函数原型: #include <stdlib.h> double strtod(const char *nptr, char **endptr); C语言及C++中的重要函数. 名称含义 ...

  6. Memcached基础知识

    主要内容: Memcached基本的工作原理 Memcached的两阶段哈希 Memcached的数据存储方式 Memcached新建Item分配内存过程 Memcached的数据过期方式 Memca ...

  7. jQuery回调、递延对象总结(中篇) —— 神奇的then方法

    前言: 什么叫做递延对象,生成一个递延对象只需调用jQuery.Deferred函数,deferred这个单词译为延期,推迟,即延迟的意思,那么在jQuery中 又是如何表达延迟的呢,从递延对象中的t ...

  8. js 判断鼠标滚轮方向

    最近因为公司项目的要求,需要做页面的全屏滚动切换效果. 页面的切换,需要脚本监听鼠标滑轮的滚动事件,来判断页面是向上切换or向下切换. 这里的脚本很简单,我就直接贴出来吧. $('html').on( ...

  9. vs2010 中无法打开 源文件 "stdafx.h" 未定义标识符 “xxx”

    解决方案: 项目属性->配置属性->C/C++->常规->附加包含目录->$(ProjectDir)

  10. 清北国庆day1 (脑)残

    (留坑) /* 不知道为什要找的循环节TM这么长 */ #include<cstdio> #include<cstdlib> #include<cstring> u ...