Spring Boot (六): 为 JPA 插上翅膀的 QueryDSL

在前面的文章中,我们介绍了 JPA 的基础使用方式,《Spring Boot (三): ORM 框架 JPA 与连接池 Hikari》,本篇文章,我们由入门至进阶的介绍一下为 JPA 插上翅膀的 QueryDSL。
1. 引言
不可否认的是 JPA 使用是非常方便的,极简化的配置,只需要使用注解,无需任何 xml 的配置文件,语义简单易懂,但是,以上的一切都建立在单表查询的前提下的,我们可以使用 JPA 默认提供的方法,简单加轻松的完成 CRUD 操作。
但是如果涉及到多表动态查询, JPA 的功能就显得有些捉襟见肘了,虽然我们可以使用注解 @Query ,在这个注解中写 SQL 或者 HQL 都是在拼接字符串,并且拼接后的字符串可读性非常的差,当然 JPA 还为我们提供了 Specification 来做这件事情,从我个人使用体验上来讲,可读性虽然还不错,但是在初学者上手的时候, Predicate 和 CriteriaBuilder 使用方式估计能劝退不少人,而且如果直接执行 SQL 连表查询,获得是一个 Object[] ,类型是什么?字段名是什么?这些都无法直观的获得,还需我们手动将 Object[] 映射到我们需要的 Model 类里面去,这种使用体验无疑是极其糟糕的。
这一切都在 QueryDSL 出世以后终结了, QueryDSL 语法与 SQL 非常相似,代码可读性非常强,异常简介优美,,并且与 JPA 高度集成,无需多余的配置,从笔者个人使用体验上来讲是非常棒的。可以这么说,只要会写 SQL ,基本上只需要看一下示例代码完全可以达到入门的级别。
2. QueryDSL 简介
QueryDSL 是一个非常活跃的开源项目,目前在 Github 上的发布的 Release 版本已经多达 251 个版本,目前最新版是 4.2.1 ,并且由 Querydsl Google组 和 StackOverflow 两个团队提供支持。
QueryDSL 是一个框架,可用于构造静态类型的类似SQL的查询。可以通过诸如 QueryDSL 之类的 API 构造查询,而不是将查询编写为内联字符串或将其外部化为XML文件。
例如,与简单字符串相比,使用 API 的好处是
IDE中的代码完成
几乎没有语法无效的查询
可以安全地引用域类型和属性
更好地重构域类型的更改
3. QueryDSL 使用实战
3.1 引入 Maven 依赖
代码清单:spring-boot-jpa-querydsl/pom.xml
<!--QueryDSL支持-->
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<scope>provided</scope>
</dependency>
<!--QueryDSL支持-->
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>
- 这里无需指定版本号,已在
spring-boot-dependencies工程中定义。
3.2 添加 Maven 插件
添加这个插件是为了让程序自动生成 query type (查询实体,命名方式为:"Q"+对应实体名)。
上文引入的依赖中 querydsl-apt 即是为此插件服务的。
注:在使用过程中,如果遇到 query type 无法自动生成的情况,用maven更新一下项目即可解决(右键项目 -> Maven -> Update Folders)。
代码清单:spring-boot-jpa-querydsl/pom.xml
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
3.3 更新和删除
在 JPA 中已经为我们提供了非常简便的更新和删除的使用方式,我们完全没有必要使用 QueryDSL 的更新和删除,不过这里还是给出用法,供大家参考:
代码清单:spring-boot-jpa-querydsl/src/main/java/com/springboot/springbootjpaquerydsl/service/impl/UserServiceImpl.java
@Override
public Long update(String id, String nickName) {
QUserModel userModel = QUserModel.userModel;
// 更新
return queryFactory.update(userModel).set(userModel.nickName, nickName).where(userModel.id.eq(id)).execute();
}
@Override
public Long delete(String id) {
QUserModel userModel = QUserModel.userModel;
// 删除
return queryFactory.delete(userModel).where(userModel.id.eq(id)).execute();
}
3.2 查询
QueryDSL 在查询这方面可以说玩的非常花了,比如一些有关 select() 和 fetch() 常用的写法如下:
代码清单:spring-boot-jpa-querydsl/src/main/java/com/springboot/springbootjpaquerydsl/service/impl/UserServiceImpl.java
@Override
public List<String> selectAllNameList() {
QUserModel userModel = QUserModel.userModel;
// 查询字段
return queryFactory.select(userModel.nickName).from(userModel).fetch();
}
@Override
public List<UserModel> selectAllUserModelList() {
QUserModel userModel = QUserModel.userModel;
// 查询实体
return queryFactory.selectFrom(userModel).fetch();
}
@Override
public List<UserDTO> selectAllUserDTOList() {
QUserModel userModel = QUserModel.userModel;
QLessonModel lessonModel = QLessonModel.lessonModel;
// 连表查询实体并将结果封装至DTO
return queryFactory
.select(
Projections.bean(UserDTO.class, userModel.nickName, userModel.age, lessonModel.startDate, lessonModel.address, lessonModel.name)
)
.from(userModel)
.leftJoin(lessonModel)
.on(userModel.id.eq(lessonModel.userId))
.fetch();
}
@Override
public List<String> selectDistinctNameList() {
QUserModel userModel = QUserModel.userModel;
// 去重查询
return queryFactory.selectDistinct(userModel.nickName).from(userModel).fetch();
}
@Override
public UserModel selectFirstUser() {
QUserModel userModel = QUserModel.userModel;
// 查询首个实体
return queryFactory.selectFrom(userModel).fetchFirst();
}
@Override
public UserModel selectUser(String id) {
QUserModel userModel = QUserModel.userModel;
// 查询单个实体,如果结果有多个,会抛`NonUniqueResultException`。
return queryFactory.selectFrom(userModel).fetchOne();
}
3.4 复杂查询操作
上面列举了简单的查询,但实际我们会遇到相当复杂的操作,比如子查询,多条件查询,多表连查,使用示例如下:
代码清单:spring-boot-jpa-querydsl/src/main/java/com/springboot/springbootjpaquerydsl/service/impl/LessonServiceImpl.java
@Service
public class LessonServiceImpl implements LessonService {
@Autowired
JPAQueryFactory queryFactory;
@Override
public List<LessonModel> findLessonList(String name, Date startDate, String address, String userId) throws ParseException {
QLessonModel lessonModel = QLessonModel.lessonModel;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
// 多条件查询示例
return queryFactory.selectFrom(lessonModel)
.where(
lessonModel.name.like("%" + name + "%")
.and(lessonModel.address.contains(address))
.and(lessonModel.userId.eq(userId))
.and(lessonModel.startDate.between(simpleDateFormat.parse("2018-12-31 00:00:00"), new Date()))
)
.fetch();
}
@Override
public List<LessonModel> findLessonDynaList(String name, Date startDate, String address, String userId) throws ParseException {
QLessonModel lessonModel = QLessonModel.lessonModel;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
// 动态查询示例
BooleanBuilder builder = new BooleanBuilder();
if (!StringUtils.isEmpty(name)){
builder.and(lessonModel.name.like("%" + name + "%"));
}
if (startDate != null) {
builder.and(lessonModel.startDate.between(simpleDateFormat.parse("2018-12-31 00:00:00"), new Date()));
}
if (!StringUtils.isEmpty(address)) {
builder.and(lessonModel.address.contains(address));
}
if (!StringUtils.isEmpty(userId)) {
builder.and(lessonModel.userId.eq(userId));
}
return queryFactory.selectFrom(lessonModel).where(builder).fetch();
}
@Override
public List<LessonModel> findLessonSubqueryList(String name, String address) {
QLessonModel lessonModel = QLessonModel.lessonModel;
// 子查询示例,并无实际意义
return queryFactory.selectFrom(lessonModel)
.where(lessonModel.name.in(
JPAExpressions
.select(lessonModel.name)
.from(lessonModel)
.where(lessonModel.address.eq(address))
))
.fetch();
}
}
3.5 Mysql 聚合函数
QueryDSL 已经内置了一些常用的 Mysql 的聚合函数,如果遇到 QueryDSL 没有提供的聚合函数也无需慌张, QueryDSL 为我们提供了 Expressions 这个类,我们可以使用这个类手动拼接一个就好,如下示例:
代码清单:spring-boot-jpa-querydsl/src/main/java/com/springboot/springbootjpaquerydsl/service/impl/UserServiceImpl.java
@Override
public String mysqlFuncDemo(String id, String nickName, int age) {
QUserModel userModel = QUserModel.userModel;
// Mysql 聚合函数示例
// 聚合函数-avg()
Double averageAge = queryFactory.select(userModel.age.avg()).from(userModel).fetchOne();
// 聚合函数-sum()
Integer sumAge = queryFactory.select(userModel.age.sum()).from(userModel).fetchOne();
// 聚合函数-concat()
String concat = queryFactory.select(userModel.nickName.concat(nickName)).from(userModel).fetchOne();
// 聚合函数-contains()
Boolean contains = queryFactory.select(userModel.nickName.contains(nickName)).from(userModel).where(userModel.id.eq(id)).fetchOne();
// 聚合函数-DATE_FORMAT()
String date = queryFactory.select(Expressions.stringTemplate("DATE_FORMAT({0},'%Y-%m-%d')", userModel.createDate)).from(userModel).fetchOne();
return null;
}
4. 小结
有关 QueryDSL 的介绍到这里就结束了,不知道各位读者看了上面的示例,有没有一种直接读 SQL 的感觉,而且这种 SQL 还是使用 OOM 的思想,将原本 Hibernate 没有做好的事情给出了一个相当完美的解决方案,上手简单易操作,而又无需写 SQL ,实际上我们操作的还是对象类。
5. 示例代码
6. 参考
Spring Boot (六): 为 JPA 插上翅膀的 QueryDSL的更多相关文章
- Spring Boot(六):如何使用mybatis
Spring Boot(六):如何使用mybatis orm框架的本质是简化编程中操作数据库的编码,发展到现在基本上就剩两家了,一个是宣称可以不用写一句SQL的hibernate,一个是可以灵活调试动 ...
- 基于Spring Boot,使用JPA动态调用Sql查询数据
在<基于Spring Boot,使用JPA操作Sql Server数据库完成CRUD>,<基于Spring Boot,使用JPA调用Sql Server数据库的存储过程并返回记录集合 ...
- 基于Spring Boot,使用JPA调用Sql Server数据库的存储过程并返回记录集合
在上一篇<基于Spring Boot,使用JPA操作Sql Server数据库完成CRUD>中完成了使用JPA对实体数据的CRUD操作. 那么,有些情况,会把一些查询语句写在存储过程中,由 ...
- 初识在Spring Boot中使用JPA
前面关于Spring Boot的文章已经介绍了很多了,但是一直都没有涉及到数据库的操作问题,数据库操作当然也是我们在开发中无法回避的问题,那么今天我们就来看看Spring Boot给我们提供了哪些疯狂 ...
- 【实验一 】Spring Boot 集成 hibernate & JPA
转眼间,2018年的十二分之一都快过完了,忙于各类事情,博客也都快一个月没更新了.今天我们继续来学习Springboot对象持久化. 首先JPA是Java持久化API,定义了一系列对象持久化的标准,而 ...
- spring boot下MultipartHttpServletRequest如何提高上传文件大小的默认值
前言: 上传下载功能算是一个非常常见的功能,如果使用MultipartHttpServletRequest来做上传功能. 不配置上传大小的话,默认是2M.在有些场景,这个肯定不能满足条件. 上传代码: ...
- 插上翅膀,让Excel飞起来——xlwings(二)
在上一篇插上翅膀,让Excel飞起来——xlwings(一)中提到利用xlwings模块,用python操作Excel有如下的优点: xlwings能够非常方便的读写Excel文件中的数据,并且能够进 ...
- 使用spring boot中的JPA操作数据库
前言 Spring boot中的JPA 使用的同学都会感觉到他的强大,简直就是神器一般,通俗的说,根本不需要你写sql,这就帮你节省了很多时间,那么下面我们来一起来体验下这款神器吧. 一.在pom中添 ...
- 从零开始的Spring Boot(3、Spring Boot静态资源和文件上传)
Spring Boot静态资源和文件上传 写在前面 从零开始的Spring Boot(2.在Spring Boot中整合Servlet.Filter.Listener的方式) https://www. ...
随机推荐
- 开发人员需要掌握的日常Linux命令集
本文整理了开发人员日常用到的linux相关命令,供参考. 文件相关 cd # 进入某个目录,不接参数进入当前用户目录(等同于cd ~)如/home/devuser,可接绝对路径或相对路径(../..表 ...
- JAVA 泛型中的通配符 T,E,K,V,?
前言 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许开发者在编译时检测到非法的类型. 泛型的本质是参数化类型,也就是说所操作的数据 ...
- [The Preliminary Contest for ICPC Asia Nanjing 2019] A-The beautiful values of the palace(二维偏序+思维)
>传送门< 前言 这题比赛的时候觉得能做,硬是怼了一个半小时,最后还是放弃了.开始想到用二维前缀和,结果$n\leq 10^{6}$时间和空间上都爆了,没有办法.赛后看题解用树状数组,一看 ...
- JavaWeb实现增删查改(图书信息管理)——之查询
关于此次CRUD所需要的jar包,本人把文件放在了百度网盘,需要的自行去下载: 链接:https://pan.baidu.com/s/1Pqe88u6aPaeVjjOq1YFQ-w 提取码:pim ...
- 【故障公告】升级阿里云 RDS SQL Server 实例故障经过
昨天晚上,我们使用的阿里云 RDS SQL Server 2008 R2 实例突然出现持续 CPU 100% 问题,后来我们通过重启实例恢复了正常(详见故障公告).但是在恢复正常后发现了新问题,这台 ...
- CodeForces 785 D Anton and School - 2 范德蒙恒等式
Anton and School - 2 题解: 枚举每个左括号作为必选的. 那么方案数就应该是下面的 1 , 然后不断化简, 通过范德蒙恒等式 , 可以将其化为一个组合数. 代码: #include ...
- yzoj P1122 阶乘 题解
T组数据,给出N,求出N!最右边非零的数. 对于30%的数据,N <= 30,T<=10. 对于全部的数据,N <= 10^2009,T<=30. 一道数学题 解析 N!/(1 ...
- yzoj1657货仓选址 题解
题面: 在一条数轴上有N家商店,它们的坐标分别为 A[1]~A[N].现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品.为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的 ...
- Allure-pytest功能特性介绍
前言 Allure框架是一个灵活的轻量级多语言测试报告工具,它不仅以web的方式展示了简介的测试结果,而且允许参与开发过程的每个人从日常执行的测试中最大限度的提取有用信息从dev/qa的角度来看,Al ...
- JSQL查询
JSQL 其特征与原生soL语句类似,并且完全面向对象,通过类名和属性访问,而不是表名和表的属性. sql:查询的是表和表中的字段 jpql:查询的是实体类和类中的属性 查询全部 >> ...