百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

为 JPA 插上翅膀的 QueryDSL(为梦想插上翅膀)

ccwgpt 2024-09-21 13:44 23 浏览 0 评论

如果我的文章对您有帮助,请关注支持下作者的公众号:极客挖掘机,获取最新干货推送:)

在前面的文章中,我们介绍了 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. 示例代码

示例代码-Github:https://github.com/meteor1993/spring-boot-examples/tree/master/spring-boot-jpa-querydsl

示例代码-Gitee:https://gitee.com/inwsy/spring-boot-examples/tree/master/spring-boot-jpa-querydsl

6. 参考

《QueryDSL 官方文档》:http://www.querydsl.com/static/querydsl/latest/reference/html/index.html

相关推荐

详解DNFSB2毒王的各种改动以及大概的加点框架

首先附上改动部分,然后逐项分析第一个,毒攻掌握技能意思是力量智力差距超过15%的话差距会被强行缩小到15%,差距不到15%则无效。举例:2000力量,1650智力,2000*0.85=1700,则智力...

通篇干货!纵观 PolarDB-X 并行计算框架

作者:玄弟七锋PolarDB-X面向HTAP的混合执行器一文详细说明了PolarDB-X执行器设计的初衷,其初衷一直是致力于为PolarDB-X注入并行计算的能力,兼顾TP和AP场景,逐渐...

字节新推理模型逆袭DeepSeek,200B参数战胜671B,豆包史诗级加强

梦晨发自凹非寺量子位|公众号QbitAI字节最新深度思考模型,在数学、代码等多项推理任务中超过DeepSeek-R1了?而且参数规模更小。同样是MoE架构,字节新模型Seed-Thinkin...

阿里智能化研发起飞!RTP-LLM 实现 Cursor AI 1000 token/s 推理技术揭秘

作者|赵骁勇阿里巴巴智能引擎事业部审校|刘侃,KittyRTP-LLM是阿里巴巴大模型预测团队开发的高性能LLM推理加速引擎。它在阿里巴巴集团内广泛应用,支撑着淘宝、天猫、高德、饿...

多功能高校校园小程序/校园生活娱乐社交管理小程序/校园系统源码

校园系统通常是为学校、学生和教职工提供便捷的数字化管理工具。综合性社交大学校园小程序源码:同城校园小程序-大学校园圈子创业分享,校园趣事,同校跑腿交友综合性论坛。小程序系统基于TP6+Uni-app...

婚恋交友系统nuiAPP前端解决上传视频模糊的问题

婚恋交友系统-打造您的专属婚恋交友平台系统基于TP6+Uni-app框架开发;客户移动端采用uni-app开发,管理后台TH6开发支持微信公众号端、微信小程序端、H5端、PC端多端账号同步,可快速打包...

已节省数百万GPU小时!字节再砍MoE训练成本,核心代码全开源

COMET团队投稿量子位|公众号QbitAI字节对MoE模型训练成本再砍一刀,成本可节省40%!刚刚,豆包大模型团队在GitHub上开源了叫做COMET的MoE优化技术。COMET已应用于字节...

通用电气完成XA102发动机详细设计审查 将为第六代战斗机提供动力

2025年2月19日,美国通用电气航空航天公司(隶属于通用电气公司)宣布,已经完成了“下一代自适应推进系统”(NGAP)计划下提供的XA102自适应变循环发动机的详细设计审查阶段。XA102是通用电气...

tpxm-19双相钢材质(双相钢f60材质)

TPXM-19双相钢是一种特殊的钢材,其独特的化学成分、机械性能以及广泛的应用场景使其在各行业中占有独特的地位。以下是对TPXM-19双相钢的详细介绍。**化学成分**TPXM-19双相钢的主要化学成...

thinkphp6里怎么给layui数据表格输送数据接口

layui官网已经下架了,但是产品还是可以使用。今天一个朋友问我怎么给layui数据表格发送数据接口,当然他是学前端的,后端不怎么懂,自学了tp框架问我怎么调用。其实官方文档上就有相应的数据格式,js...

完美可用的全媒体广告精准营销服务平台PHP源码

今天测试了一套php开发的企业网站展示平台,还是非常不错的,下面来给大家说一下这套系统。1、系统架构这是一套基于ThinkPHP框架开发的HTML5响应式全媒体广告精准营销服务平台PHP源码。现在基于...

一对一源码开发,九大方面完善基础架构

以往的直播大多数都是一对多进行直播社交,弊端在于不能满足到每个用户的需求,会降低软件的体验感。伴随着用户需求量的增加,一对一直播源码开始出现。一个完整的一对一直播流程即主播发起直播→观看进入房间观看→...

Int J Biol Macromol .|交联酶聚集体在分级共价有机骨架上的固定化:用于卤代醇不对称合成的高稳定酶纳米反应器

大家好,今天推送的文章发表在InternationalJournalofBiologicalMacromolecules上的“Immobilizationofcross-linkeden...

【推荐】一款开源免费的 ChatGPT 聊天管理系统,支持PC、H5等多端

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!项目介绍GPTCMS是一款开源且免费(基于GPL-3.0协议开源)的ChatGPT聊天管理系统,它基于先进的GPT...

高性能计算(HPC)分布式训练:训练框架、混合精度、计算图优化

在深度学习模型愈发庞大的今天,分布式训练、高效计算和资源优化已成为AI开发者的必修课。本文将从数据并行vs模型并行、主流训练框架(如PyTorchDDP、DeepSpeed)、混合精度训练(...

取消回复欢迎 发表评论: