基于 Spring-Jpa 的动态 Sql 工具类分享

前言

为什么开发工具类?

在这里先贴一段代码
代码
如果项目用过 Spring-Jpa 的人对这种写法应该都不会陌生,反正我们公司是这样用的,每一个可能用到的查询字段都要手动写这样一段逻辑,算上 count 的话相似的逻辑要写4遍,笔者真的很讨厌这种重复度高的代码,所以在写了几次后就开始构思工具类来缓解这一情况。

工具类的简单介绍

设计思路

  1. 通过 Java 提供的反射特性动态判断每一个字段是否存在并拼装 sql
  2. 使用自定义注解来标识需要查询的字段和字段对应的 sql 语句
  3. 频繁获取反射方法会大大降低性能,关键的数据需要封装并存到缓存
  4. 考虑到各种场景,工具类必须满足泛用性高扩展性

工具类的优缺点

从23年9月份开发出最初版本,使用到现在已经有半年多了,实际工作中也一直在完善和迭代,目前可以说功能已经趋近稳定,现在站在使用者和开发者的角度来聊聊工具类的优缺点(都是一己之见

  1. 统一了代码风格
  2. 大大提高了开发效率
  3. 由于代码的一体化,避免了许多可能因为粗心 copy 导致的 bug
  4. 个人感觉现在已经可以覆盖工作中 80% 关于 sql 的场景
  5. 大部分功能都使用 JDK 自带类实现,依赖少,10分钟便可移植到新项目

快速上手

这里我准备了一个 SpringBoot 项目用于演示,项目的源码会放在文章最下方,大家导入自己的 idea 就可以跑起来了,文章中的功能介绍只会贴截图,now gogogo!

工具类的基础使用

准备工作

在使用工具类的第一种和第二种用法时,需要在类相关的字段上添加自定义注解

自定义注解介绍

在这里插入图片描述
在这里插入图片描述

自定义注解中存在 3 个属性

  1. name: 属性对应的名称,默认为属性本身,也可以映射数据库字段,比如有一属性 address, 其对应数据库字段 dz,那么不指定 name 和 指定 name = “address” 是等效的,也可以指定 name = “dz”
  2. operator: 属性用于拼接 sql 的运算符, 默认为 EQUALS 即 “=”, 具体支持的 opeartor 看图2
  3. note: 字段的注释,默认为空,特定方法会用到,写不写都行

自定义注解使用图例

在这里插入图片描述

唯一需要注意的是当字段使用 IN 或 NOT_IN 时,一定要指定 name

第一种用法

用法简介

  1. 将一切托管给工具类,自己只需要关注业务逻辑的实现
  2. 上手简单,代码简洁
  3. 不够灵活,不支持手动操作 Sql,涉及到复杂场景或多表时力有不逮
  4. 推荐使用场景:只涉及单表的简单增删改查

使用图例

PostMan调用实例
  1. Controller代码图
    在这里插入图片描述
    注意要点

    • 构造 ConditionSqlBean 对象
    • 指定目标对象(必须指定, 不然条件从哪来?)
  2. Service代码图
    在这里插入图片描述
    注意要点

    • 使用 ConditionSqlUtil.simpleFindCondition 完成查询
    • 查询完成后的结果会包装到传入的 sqlBean 对象中
    • sqlBean.getResultList() 获取结果集合
    • sqlBean.getTotal() 获取集合总数
  3. PostMan 实操与结果
    PostMan图1
    在这里插入图片描述

PostMan 图1对应 Sql
在这里插入图片描述

PostMan 图2
在这里插入图片描述

PostMan 图2对应 Sql
在这里插入图片描述

junit 测试单元使用实例

在这里插入图片描述

第二种用法

用法简介

  1. 本质是第一种用法的解耦,将 ConditionSqlUtil.simpleFindCondition() 中的流程手动执行
  2. 在自动构造的基础上保留了自定义操作 Sql 的空间
  3. 代码不够简洁,List 查询和 Count 查询都需要手写
  4. 在多表联合查询时只能自动构建主表的条件

使用图例

为了模拟复杂场景,我们再简单添加一张表
在这里插入图片描述

PostMan 调用实例
  1. Controller 层与第一种用法一样,不做赘述
  2. Service 层代码
    在这里插入图片描述
    需要注意的点
    • 指定 headSql 时主表一定要映射成 t
    • 构造完 sql 后要调用 buildSqlByOrder, 否则 order by 不会生效
    • List一遍,Count也还有一遍
  3. PostMan 实操与结果
    PostMan 图1
    在这里插入图片描述
    图一构造的Sql
    在这里插入图片描述

第三种用法

用法简介

  1. 与第一、第二种不同,第三种用法不需要在属性上指定 @Condtion 注解,真正的开箱即用
  2. 每个可能需要查询的字段都需要手动指定
  3. 不推荐用在需要与前端交互的综合查询接口中,建议用来替代 repository 中的方法
  4. 此用法的核心是通过工具类的内部类 DirectInfo 来拼装Sql
  5. DirectInfo 图示
    在这里插入图片描述
  6. 使用时不推荐手动 new DirectInfo ,请使用工具类提供的 addDirectInfo 方法来添加此属性
    在这里插入图片描述

使用图例

条件简单场景

在这里插入图片描述
对应Sql
在这里插入图片描述

需要指定字段的场景

在这里插入图片描述
对应Sql
在这里插入图片描述

多表联合查询复杂场景

在这里插入图片描述
对应Sql
在这里插入图片描述

工具类的高级用法

介绍

  1. 建议看完了上面的3种方式对工具类有了基本的了解和实操后再看这里
  2. 在实际工作中,基础的使用占大多数,但仍然存在一些麻烦的字段,比如 排序,范围,多变的查询的规则等,为了偷懒,为了泛用性,工具类提供了几个自定义字段和方法便于操作这类字段
  3. 请注意:下面的相关属性都有两种方式放入 ConditionSqlBean 对象中,一为 sqlBean.set() 相关属性,二是将相关属性放到目标 object 中,当你执行 sqlBean.setObject() 时会自动解析相关属性并存入 sqlBean 中(理解可能有点困难,建议自己去看看代码并试着操作)

特定范围属性

  1. ConditionSqlBean 中存在属性 rangeFieldInfos 用于标识那些需要指定范围的属性, 用法一、二、三都支持此字段
    在这里插入图片描述
  2. 属性介绍
    • rangeField: 属性名称
    • rangeGreat: 属性需要大于的值
    • rangeLess: 属性需要小于的值
  3. 具体使用实例
    PostMan传参方式
    在这里插入图片描述

代码添加方式,注意:代码中添加属性请使用 sqlBean 对象提供的 addRangeFieldInfo 来添加
在这里插入图片描述
对应Sql
在这里插入图片描述

排序属性

  1. ConditionSqlBean 中存在属性 orderMap 用于标识那些需要指定排序的属性, 用法一、二、三都支持此字段
  2. orderMap 是一个 LinkedHashMap, 键值对分别为 属性名称 和 asc/desc
  3. 具体使用示例
    PostMan传参
    在这里插入图片描述
    代码中指定,请使用 addOrderMap 方法
    在这里插入图片描述
    对应Sql
    在这里插入图片描述

查询中忽略的属性

  1. ConditionSqlBean 中存在属性 ignoreFieldSet 用于标识那些查询中不要的属性, 用法一、二、三都支持此字段
  2. 使用实例(这里我传了 name 字段,但因为其在 ignoreFieldSet 中,所以作为 Sql 条件)
    PostMan传参
    在这里插入图片描述
    代码中指定,请使用 addIgnoreField() 方法添加
    在这里插入图片描述
    对应Sql
    在这里插入图片描述

动态定义查询条件

  1. ConditionSqlBean 中存在属性 selectRules 用于标识动态条件 用法一、二支持此字段
  2. tips:这个功能当时是考虑到 @Condition 注解指定的条件和实际条件不符,而我又不想改代码的情况,虽然写出来了,但实际从未使用过,甚至一度将其遗忘…
  3. 使用实例(这里我将id的查询条件从 EQUALS 改为了 LIKE
    PostMan传参
    在这里插入图片描述

对应Sql
在这里插入图片描述

只查询指定字段

  1. ConditionSqlBean 中存在属性 needFields 用于指定需要查询的字段 用法一、二、三支持此字段
  2. 注意: 请指定两个字段及以上,否则可能出问题
  3. 使用实例
    PostMan传参
    在这里插入图片描述
    对应Sql
    在这里插入图片描述

工具类性能测试与对比

  1. 由于工具类中频繁用到了反射,所以有些人可能会认为会有性能瓶颈
  2. 但经过我的研究,反射消耗性能的地方大部分在反射获取方法属性的时候
  3. 因此若将获取到的方法通过缓存储存的话,性能将不会收到影响
  4. 接下来是测试1,对比正常方式和工具类,可以看到在正常情况下初始化过后工具类执行速度并不慢
    在这里插入图片描述
  5. 接下来是测试2,各循环 10000 次,结果有点抽象,工具类反而要稍微快一点…
    在这里插入图片描述
  6. 综上所述,大家放心用就行了

使用中可能出现的问题与调试思路

简介

  1. 许多同事用过工具类后都会反应跟代码找问题困难,其实只是没有找到调试的方法,因此这里提供基础排查思路

关于环境

  1. 若您想在自己的项目中使用 ConditionSqlUtil,需要确保将演示项目中以下几个类放入项目中
    • ConditionSqlBean
    • Condition
    • OperatorEnum
    • ConditionSqlUtil
    • SpringUtil
    • SystemCollectionUtil
  2. 工具类的强相关依赖只有 Spring-Jpa ,额,演示项目 pom 文件里没有找到…,那么只要确保您的项目有SpringBoot相关依赖包应该就可以正常使用了
  3. 工具类所在的包需要被 Spring 注册,特别是 SystemCollectionUtil ,这个类很重要,相关的反射缓存都是通过这个类获取
  4. 在把所有的报错都清了后应该就可以正常使用了

如何调试

  1. 无论您使用哪种方法,我都推荐调试时在Sql构造完毕的地方打断点,即下图这句话,无论哪种方式都会存在,若您的 Sql 构造准确无误且符合预期,那么 95% 的情况都不会有问题,如果真的出现 sql 构造成功但结果与预期不符,请务必联系我解决bug
    在这里插入图片描述
  2. 若您的程序无法成功构建 sql,即进入不到上图断点时,大概率是环境有问题,此时请检查 sqlBean 中的 entityClass 字段和 entityManager 字段是否正确!
    在这里插入图片描述
  3. 若成功构建 sql 但字段不符合您的预期,那么您可以检查 sqlBean 中的 fieldOperatorMap(属性和条件对应关系) 集合和 fieldDefinedMap(定义属性和具体属性定义关系 ,比如 inIds -> id) 集合中的数据是否正确
    在这里插入图片描述
  4. 若排序、范围等条件构建失败,您可以尝试通过断点进入对应方法查看构建详情,工具类中所有步骤都有封装,定位明确
    在这里插入图片描述
    在这里插入图片描述

写在最后

这个工具类我自己是对其质量是非常满意的,将其免费开源出来还是纠结了一小会,把所有注释都删掉是我最后的倔强了🤣,如果有帮到你就帮我点个赞或收藏再走吧,最后贴上演示项目
链接:https://pan.baidu.com/s/1alSDMrTn5e6ZbI4Zutfk3w?pwd=626s
提取码:626s
–来自百度网盘超级会员V9的分享

  • 17
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值