Spring Data JPA进阶——Specifications和Querydsl
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的更多相关文章
- Spring Data JPA 的 Specifications动态查询
主要的结构: 有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询. ...
- Spring Data JPA 进阶
Java持久化查询语言概述 Java持久化查询语言(JPQL)是一种可移植的查询语言,旨在以面向对象表达式语言的表达式,将SQL语法和简单查询语义绑定在一起,使用这种语言编写的查询是可移植的,可以被编 ...
- 一文搞定 Spring Data JPA
Spring Data JPA 是在 JPA 规范的基础上进行进一步封装的产物,和之前的 JDBC.slf4j 这些一样,只定义了一系列的接口.具体在使用的过程中,一般接入的是 Hibernate 的 ...
- 深入探索Spring Data JPA, 从Repository 到 Specifications 和 Querydsl
数据访问层,所谓的CRUD是后端程序员的必修课程,Spring Data JPA 可以让我们来简化CRUD过程,本文由简入深,从JPA的基本用法,到各种高级用法. Repository Spring ...
- Spring Data JPA教程, 第五部分: Querydsl(未翻译)
The fourth part of my Spring Data JPA tutorialdescribed how you can implement more advanced queries ...
- 如何在Spring Data JPA中引入Querydsl
一.环境说明 基础框架采用Spring Boot.Spring Data JPA.Hibernate.在动态查询中,有一种方式是采用Querydsl的方式. 二.具体配置 1.在pom.xml中,引入 ...
- Spring Data JPA系列3:JPA项目中核心场景与进阶用法介绍
大家好,又见面了. 到这里呢,已经是本SpringData JPA系列文档的第三篇了,先来回顾下前面两篇: 在第1篇<Spring Data JPA系列1:JDBC.ORM.JPA.Spring ...
- Spring data JPA中使用Specifications动态构建查询
有时我们在查询某个实体的时候,给定的条件是不固定的,这是我们就需要动态 构建相应的查询语句,在JPA2.0中我们可以通过Criteria接口查询,JPA criteria查询.相比JPQL,其优势是类 ...
- Spring Data JPA教程, 第四部分: JPA Criteria Queries(未翻译)
The third part of my Spring Data JPA tutorialdescribed how you can create custom queries by using qu ...
随机推荐
- 【MVC5】画面多按钮提交
画面上有个多个按钮时,如何绑定到各自的Action上? 1.追加如下MultipleButtonAttribute类 1 using System; 2 using System.Reflection ...
- Mongodb的Samus驱动
最近开始学习Mongodb方面的东西.. 看到有很多博主都说MongoDB的第三方驱动 Samus 对Linq的支持比较好..能够降低学习的成本..所以就想从这里开始.. 但是弊端在我学习了一半的时候 ...
- Hibernate 查询MatchMode的四种模式
Hibernate 查询MatchMode的四种模式 MatchMode.START:字符串在最前面的位置.相当于"like 'key%'" MatchMode.END:字符串在最 ...
- Mysql分表和分区的区别、分库分表介绍与区别
分表和分区的区别: 一,什么是mysql分表,分区 什么是分表,从表面意思上看呢,就是把一张表分成N多个小表,具体请看:mysql分表的3种方法 什么是分区,分区呢就是把一张表的数据分成N多个区块,这 ...
- MySQL关键字(保留字)列表
在使用MySQL的时候,一般尽量避免用关键字作为表名,如使用关键字做表名,需要按标准写法给SQL语句加[](或是“)区分字段名和表名. 下面列出MySQL所有关键字,希望给使用MySQL的朋友提供一些 ...
- Linux下的文件及文件后缀名
Linux下的文件及文件后缀名 2013-03-14 15:34 6969人阅读 评论(0) 收藏 举报 ++++++++++++++++++++++++++++++++++++++正文+++++++ ...
- 单例模式singleton
在进行开发的时候,我们在有些情形下有些对象我们只需要一个.例如:配置文件.工具类.线程池.缓存.日志对象等. 如何保证我们的对象只有一个呢?我们可以通过单例来实现. 常用的单例有两种:饿汉模式和懒汉模 ...
- 第一个C++例子
#include <iostream> using namespace std; class Time { private: int hour; int minute; int secon ...
- ajax浅析---ScriptManagerProxy
使用ScriptManagerProxy控件 在ASP.NET AJAX中,由于一个ASPX页面上只能有一个ScriptManager控件,所以在有母版页的情况下,如果需要在Master-Page和C ...
- hdu.1010.Tempter of the Bone(dfs+奇偶剪枝)
Tempter of the Bone Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Othe ...