在前面的文章中,我们介绍了 JPA 的基础使用方式,《Spring Boot (三): ORM 框架 JPA 与连接池 Hikari》,本篇文章,我们由入门至进阶的介绍一下为 JPA 插上翅膀的 QueryDSL。

1. 引言

不可否认的是 JPA 使用是非常方便的,极简化的配置,只需要使用注解,无需任何 xml 的配置文件,语义简单易懂,但是,以上的一切都建立在单表查询的前提下的,我们可以使用 JPA 默认提供的方法,简单加轻松的完成 CRUD 操作。

但是如果涉及到多表动态查询, JPA 的功能就显得有些捉襟见肘了,虽然我们可以使用注解 @Query ,在这个注解中写 SQL 或者 HQL 都是在拼接字符串,并且拼接后的字符串可读性非常的差,当然 JPA 还为我们提供了 Specification 来做这件事情,从我个人使用体验上来讲,可读性虽然还不错,但是在初学者上手的时候, PredicateCriteriaBuilder 使用方式估计能劝退不少人,而且如果直接执行 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;// 连表查询实体并将结果封装至DTOreturn 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 {@AutowiredJPAQueryFactory queryFactory;@Overridepublic 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();}@Overridepublic 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();}@Overridepublic 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. 示例代码

示例代码-Github

示例代码-Gitee

6. 参考

《QueryDSL 官方文档》

如果我的文章对您有帮助,请扫码关注下作者的公众号:获取最新干货推送:)

Spring Boot (六): 为 JPA 插上翅膀的 QueryDSL相关推荐

  1. 炼丹秘术:给Embedding插上翅膀

    在实践中,推荐系统利用Deep Learning去生成Embedding,然后通过Embedding在召回层进行召回是一种常用的方法,而且这种方法在效果和响应速度上也不比多路召回差. 同时,在局部敏感 ...

  2. feachall php_集合:给 PHP 数组插上翅膀

    集合:给 PHP 数组插上翅膀 由 学院君 创建于2年前, 最后更新于 9个月前 版本号 #2 35657 views 7 likes 1 collects 简介 Illuminate\Support ...

  3. Spring Boot下使用JPA报错:'hibernate.dialect' not set的解决办法

    问题现象: Spring Boot下使用JPA报错:'hibernate.dialect' not set 原因是: 没有设置数据库方言导致的 解决方案: 1.如果配置文件格式为application ...

  4. 北斗时钟系统(网络授时服务器)为数字化变电站插上翅膀

    北斗时钟系统(网络授时服务器)为数字化变电站插上翅膀 北斗时钟系统(网络授时服务器)为数字化变电站插上翅膀 [摘要]本文介绍了电力系统目前所采用的时间同步方案技术的局限性以及存在的问题.在此基础上,提 ...

  5. spring boot 整合 阿里云oss上传

    Spring Boot 整合 阿里云OSS上传 OSS申请和配置 1. 注册登录 2.开通以及配置 springboot整合使用 1. 进入我们springboot的项目中,导入oss相关依赖 2. ...

  6. 龙光地产补声东:数字化运营让业务插上翅膀|2021中国房地产数字峰会

     关注ITValue,看企业级最新鲜.最价值报道! 编者按:首届50+地产数字化最高决策者齐聚的"中国房地产数字峰会"已经圆满落幕.这场由中国房地产业协会指导.中国房地产业协会数字 ...

  7. 让沉默的大数据为人工智能插上翅膀

    让沉默的大数据为人工智能插上翅膀 运筹学能够让人工智能"学"会举一反三,从目前的解决具体问题发展为解决类型化的多种问题-- 应用广泛的人工智能.酷炫的黑科技,在不久前闭幕的重庆国际 ...

  8. 给科技插上翅膀,中兴以5G技术开启万物互联之旅

    随着5G行业应用场景的不断扩展及人工智能的发展,5G即将迎来新一轮技术革新.从生活到生产,5G技术无所不在,从人与人连接,到人与物.物与物连接,中兴通讯正以5G技术开启万物互联之旅:如在产品领域推出5 ...

  9. spring boot接收微信小程序上传的文件

    spring boot接收微信小程序上传的文件,首先前台传给我们后端的不是一个路径,而是以一个文件类型传递给我,这时我们在controller层接收时就可以用MultipartFile进行接收,如果接 ...

最新文章

  1. 对抗神经网络GAN到底学到了什么
  2. php删除session中的值,PHP中session变量的销毁
  3. 华为发布会: 牛逼鸿蒙,吹水的大会
  4. CentOS 7下安装jdk1.8
  5. mysql中in的用法
  6. Linq to Sql : 三种事务处理方式
  7. 海洋CMS仿RiPro主题风格自适应模板
  8. [FWT] UOJ #310. 【UNR #2】黎明前的巧克力
  9. 「学术放养」和「认真负责」并不冲突,芝大CS博士谈从导师身上学到的几件事...
  10. 2020神舟几号发射_今年将发射神舟十一号载人飞船 2020年将建成载人空间站
  11. 伪C++开发连连看(补充)
  12. 测试人员如何做好需求分析
  13. python代码画人物_用Python+Gephi画《人民的名义》人物关系图
  14. c语言编写4个子函数用主函数调用,哪位师傅知道51单片机怎样编写子程序?C语言的。在主程序里调...
  15. 计算机无法启动vm服务,电脑中的虚拟机VM开机停留在dhcp无法启动如何解决
  16. vue中脚手架设置自动打开浏览器打开后地址为http://0.0.0.0:8080/
  17. 线上盲盒电商模式运营
  18. [2021.10.14][Android P]OpenCamera详细分析(Camera2+Hal3)
  19. 愤世嫉俗的程序员,总在某乎发表言论,当起了“键盘侠”
  20. 技术人攻略访谈三十八-许式伟:十一年逆流顺流,首席架构师到CEO

热门文章

  1. 输入一个数字,在数组中查询是否存在,如果存在则显示其索引
  2. 顶级 Swift 服务端框架对决 Node.js
  3. mysql勒索_mysql数据库被勒索删库怎么办
  4. pythonif语句怎么换行输入_李沁和李易峰演的电视剧叫什么名字
  5. virtualBox虚拟机NAT上网方式并实现主机虚拟机双向通信教程
  6. 【SemiDrive源码分析】【MailBox核间通信】42 - 基于Mailbox 实现的 mailbox_demo 应用程序(RTOS Android侧通信实现)
  7. AirPods 无法连接到iPhone、iPad或Mac的解决办法
  8. 往往取决于他所遇到困难的程度
  9. 关于室内分布系统中合路器、功分器、耦合器的对比
  10. 【从线性回归到 卷积神经网络CNN 循环神经网络RNN Pytorch 学习笔记 目录整合 源码解读 B站刘二大人 绪论(0/10)】