基于 Spring-Jpa 的动态 Sql 查询工具类分享
前言
为什么开发工具类?
在这里先贴一段代码
如果项目用过 Spring-Jpa 的人对这种写法应该都不会陌生,反正我们公司是这样用的,每一个可能用到的查询字段都要手动写这样一段逻辑,算上 count 的话相似的逻辑要写4遍,笔者真的很讨厌这种重复度高的代码,所以在写了几次后就开始构思工具类来缓解这一情况。
工具类的简单介绍
设计思路
- 通过 Java 提供的反射特性动态判断每一个字段是否存在并拼装 sql
- 使用自定义注解来标识需要查询的字段和字段对应的 sql 语句
- 频繁获取反射方法会大大降低性能,关键的数据需要封装并存到缓存中
- 考虑到各种场景,工具类必须满足泛用性和高扩展性
工具类的优缺点
从23年9月份开发出最初版本,使用到现在已经有半年多了,实际工作中也一直在完善和迭代,目前可以说功能已经趋近稳定,现在站在使用者和开发者的角度来聊聊工具类的优缺点(都是一己之见)
- 统一了代码风格
- 大大提高了开发效率
- 由于代码的一体化,避免了许多可能因为粗心 copy 导致的 bug
- 个人感觉现在已经可以覆盖工作中 80% 关于 sql 的场景
- 大部分功能都使用 JDK 自带类实现,依赖少,10分钟便可移植到新项目
快速上手
这里我准备了一个 SpringBoot 项目用于演示,项目的源码会放在文章最下方,大家导入自己的 idea 就可以跑起来了,文章中的功能介绍只会贴截图,now gogogo!
工具类的基础使用
准备工作
在使用工具类的第一种和第二种用法时,需要在类相关的字段上添加自定义注解
自定义注解介绍
自定义注解中存在 3 个属性
- name: 属性对应的名称,默认为属性本身,也可以映射数据库字段,比如有一属性 address, 其对应数据库字段 dz,那么不指定 name 和 指定 name = “address” 是等效的,也可以指定 name = “dz”
- operator: 属性用于拼接 sql 的运算符, 默认为 EQUALS 即 “=”, 具体支持的 opeartor 看图2
- note: 字段的注释,默认为空,特定方法会用到,写不写都行
自定义注解使用图例
唯一需要注意的是当字段使用 IN 或 NOT_IN 时,一定要指定 name
第一种用法
用法简介
- 将一切托管给工具类,自己只需要关注业务逻辑的实现
- 上手简单,代码简洁
- 不够灵活,不支持手动操作 Sql,涉及到复杂场景或多表时力有不逮
- 推荐使用场景:只涉及单表的简单增删改查
使用图例
PostMan调用实例
-
Controller代码图
注意要点- 构造 ConditionSqlBean 对象
- 指定目标对象(必须指定, 不然条件从哪来?)
-
Service代码图
注意要点- 使用 ConditionSqlUtil.simpleFindCondition 完成查询
- 查询完成后的结果会包装到传入的 sqlBean 对象中
- sqlBean.getResultList() 获取结果集合
- sqlBean.getTotal() 获取集合总数
-
PostMan 实操与结果
PostMan图1
PostMan 图1对应 Sql
PostMan 图2
PostMan 图2对应 Sql
junit 测试单元使用实例
第二种用法
用法简介
- 本质是第一种用法的解耦,将 ConditionSqlUtil.simpleFindCondition() 中的流程手动执行
- 在自动构造的基础上保留了自定义操作 Sql 的空间
- 代码不够简洁,List 查询和 Count 查询都需要手写
- 在多表联合查询时只能自动构建主表的条件
使用图例
为了模拟复杂场景,我们再简单添加一张表
PostMan 调用实例
- Controller 层与第一种用法一样,不做赘述
- Service 层代码
需要注意的点- 指定 headSql 时主表一定要映射成 t
- 构造完 sql 后要调用 buildSqlByOrder, 否则 order by 不会生效
- List一遍,Count也还有一遍
- PostMan 实操与结果
PostMan 图1
图一构造的Sql
第三种用法
用法简介
- 与第一、第二种不同,第三种用法不需要在属性上指定 @Condtion 注解,真正的开箱即用
- 每个可能需要查询的字段都需要手动指定
- 不推荐用在需要与前端交互的综合查询接口中,建议用来替代 repository 中的方法
- 此用法的核心是通过工具类的内部类 DirectInfo 来拼装Sql
- DirectInfo 图示
- 使用时不推荐手动 new DirectInfo ,请使用工具类提供的 addDirectInfo 方法来添加此属性
使用图例
条件简单场景
对应Sql
需要指定字段的场景
对应Sql
多表联合查询复杂场景
对应Sql
工具类的高级用法
介绍
- 建议看完了上面的3种方式对工具类有了基本的了解和实操后再看这里
- 在实际工作中,基础的使用占大多数,但仍然存在一些麻烦的字段,比如 排序,范围,多变的查询的规则等,
为了偷懒,为了泛用性,工具类提供了几个自定义字段和方法便于操作这类字段 - 请注意:下面的相关属性都有两种方式放入 ConditionSqlBean 对象中,一为 sqlBean.set() 相关属性,二是将相关属性放到目标 object 中,当你执行 sqlBean.setObject() 时会自动解析相关属性并存入 sqlBean 中(理解可能有点困难,建议自己去看看代码并试着操作)
特定范围属性
- ConditionSqlBean 中存在属性 rangeFieldInfos 用于标识那些需要指定范围的属性, 用法一、二、三都支持此字段
- 属性介绍
- rangeField: 属性名称
- rangeGreat: 属性需要大于的值
- rangeLess: 属性需要小于的值
- 具体使用实例
PostMan传参方式
代码添加方式,注意:代码中添加属性请使用 sqlBean 对象提供的 addRangeFieldInfo 来添加
对应Sql
排序属性
- ConditionSqlBean 中存在属性 orderMap 用于标识那些需要指定排序的属性, 用法一、二、三都支持此字段
- orderMap 是一个 LinkedHashMap, 键值对分别为 属性名称 和 asc/desc
- 具体使用示例
PostMan传参
代码中指定,请使用 addOrderMap 方法
对应Sql
查询中忽略的属性
- ConditionSqlBean 中存在属性 ignoreFieldSet 用于标识那些查询中不要的属性, 用法一、二、三都支持此字段
- 使用实例(这里我传了 name 字段,但因为其在 ignoreFieldSet 中,所以作为 Sql 条件)
PostMan传参
代码中指定,请使用 addIgnoreField() 方法添加
对应Sql
动态定义查询条件
- ConditionSqlBean 中存在属性 selectRules 用于标识动态条件 用法一、二支持此字段
- tips:这个功能当时是考虑到 @Condition 注解指定的条件和实际条件不符,而我又不想改代码的情况,虽然写出来了,但实际从未使用过,甚至一度将其遗忘…
- 使用实例(这里我将id的查询条件从 EQUALS 改为了 LIKE)
PostMan传参
对应Sql
只查询指定字段
- ConditionSqlBean 中存在属性 needFields 用于指定需要查询的字段 用法一、二、三支持此字段
- 注意: 请指定两个字段及以上,否则可能出问题
- 使用实例
PostMan传参
对应Sql
工具类性能测试与对比
- 由于工具类中频繁用到了反射,所以有些人可能会认为会有性能瓶颈
- 但经过我的研究,反射消耗性能的地方大部分在反射获取方法属性的时候
- 因此若将获取到的方法通过缓存储存的话,性能将不会收到影响
- 接下来是测试1,对比正常方式和工具类,可以看到在正常情况下初始化过后工具类执行速度并不慢
- 接下来是测试2,各循环 10000 次,结果有点抽象,工具类反而要稍微快一点…
- 综上所述,大家放心用就行了
使用中可能出现的问题与调试思路
简介
- 许多同事用过工具类后都会反应跟代码找问题困难,其实只是没有找到调试的方法,因此这里提供基础排查思路
关于环境
- 若您想在自己的项目中使用 ConditionSqlUtil,需要确保将演示项目中以下几个类放入项目中
- ConditionSqlBean
- Condition
- OperatorEnum
- ConditionSqlUtil
- SpringUtil
- SystemCollectionUtil
- 工具类的强相关依赖只有 Spring-Jpa ,额,演示项目 pom 文件里没有找到…,那么只要确保您的项目有SpringBoot相关依赖包应该就可以正常使用了
- 工具类所在的包需要被 Spring 注册,特别是 SystemCollectionUtil ,这个类很重要,相关的反射缓存都是通过这个类获取
- 在把所有的报错都清了后应该就可以正常使用了
如何调试
- 无论您使用哪种方法,我都推荐调试时在Sql构造完毕的地方打断点,即下图这句话,无论哪种方式都会存在,若您的 Sql 构造准确无误且符合预期,那么 95% 的情况都不会有问题,如果真的出现 sql 构造成功但结果与预期不符,请务必联系我解决bug
- 若您的程序无法成功构建 sql,即进入不到上图断点时,大概率是环境有问题,此时请检查 sqlBean 中的 entityClass 字段和 entityManager 字段是否正确!
- 若成功构建 sql 但字段不符合您的预期,那么您可以检查 sqlBean 中的 fieldOperatorMap(属性和条件对应关系) 集合和 fieldDefinedMap(定义属性和具体属性定义关系 ,比如 inIds -> id) 集合中的数据是否正确
- 若排序、范围等条件构建失败,您可以尝试通过断点进入对应方法查看构建详情,工具类中所有步骤都有封装,定位明确
写在最后
这个工具类我自己是对其质量是非常满意的,将其免费开源出来还是纠结了一小会,把所有注释都删掉是我最后的倔强了🤣,如果有帮到你就帮我点个赞或收藏再走吧,最后贴上演示项目
链接:https://pan.baidu.com/s/1alSDMrTn5e6ZbI4Zutfk3w?pwd=626s
提取码:626s
–来自百度网盘超级会员V9的分享