Springboot 自定义 Mybatis拦截器,实现 动态查询条件SQL自动组装拼接(玩具)
前言
ps:最近在参与3100保卫战,战况很激烈,刚刚打完仗,来更新一下之前写了一半的博客。
该篇针对日常写查询的时候,那些动态条件sql 做个简单的封装,自动生成(抛砖引玉,搞个小玩具,不喜勿喷)。
正文
来看看我们平时写那些查询,基本上都要写的一些动态sql:
一个字段写一个if ,有没有人觉得烦的。
每张表的查询,很多都有这种需求,根据什么查询,根据什么查询,不为空就触发条件。
天天写天天写,copy 改,copy改, 有没有人觉得烦的。
可能有看官看到这就会说, 用插件自动生成就好了。
也有看官会说,用mybatis-plus就好了。
确实有道理,但是我就是想整个小玩具。你管我。
开整
本篇实现的封装小玩具思路:
①制定的规则(比如标记自定义注解 @JcSqlQuery 或是 函数命名带上JcDynamics)。
② 触发的查询符合规则的, 都自动去根据传参对象,不为空就自动组装 sql查询条件。
③ 利用mybatis @Select 注解,把默认表查询sql写好,顺便进到自定义的mybatis拦截器里面。
④组装完sql,就执行,完事。
先写mapper函数 :
/*** @Author JCccc* @Description* @Date 2023/12/14 16:56*/
@Mapper
public interface DistrictMapper {@Select("select code,name,parent_code,full_name FROM s_district_info")List<District> queryListJcDynamics(District district);@Select("select code,name,parent_code,full_name FROM s_district_info")District queryOneJcDynamics(District district);}
然后是ParamClassInfo.java 这个用于收集需要参与动态sql组装的类:
import lombok.Data;/*** @Author JCccc* @Description* @Date 2021/12/14 16:56*/
@Data
public class ParamClassInfo {private String classType;private Object keyValue;private String keyName;}
然后是一个自定义的mybatis拦截器(这里面写了一些小函数实现自主组装,下面有图解) :
MybatisInterceptor.java
import com.example.dotest.entity.ParamClassInfo;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;import static java.util.regex.Pattern.*;/*** @Author JCccc* @Description* @Date 2021/12/14 16:56*/
@Component
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class MybatisInterceptor implements Interceptor {private final static String JC_DYNAMICS = "JcDynamics";@Overridepublic Object intercept(Invocation invocation) throws Throwable {//获取执行参数Object[] objects = invocation.getArgs();MappedStatement ms = (MappedStatement) objects[0];Object objectParam = objects[1];List<ParamClassInfo> paramClassInfos = convertParamList(objectParam);String queryConditionSqlScene = getQueryConditionSqlScene(paramClassInfos);//解析执行sql的map方法,开始自定义规则匹配逻辑String mapperMethodAllName = ms.getId();int lastIndex = mapperMethodAllName.lastIndexOf(".");String mapperClassStr = mapperMethodAllName.substring(0, lastIndex);String mapperClassMethodStr = mapperMethodAllName.substring((lastIndex + 1));Class<?> mapperClass = Class.forName(mapperClassStr);Method[] methods = mapperClass.getMethods();for (Method method : methods) {if (method.getName().equals(mapperClassMethodStr) && mapperClassMethodStr.contains(JC_DYNAMICS)) {BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]);String originalSql = boundSql.getSql().toLowerCase(Locale.CHINA).replace("[\\t\\n\\r]", " ");//进行自动的 条件拼接String newSql = originalSql + queryConditionSqlScene;BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), newSql,boundSql.getParameterMappings(), boundSql.getParameterObject());MappedStatement newMs = newMappedStatement(ms, new MyBoundSqlSqlSource(newBoundSql));for (ParameterMapping mapping : boundSql.getParameterMappings()) {String prop = mapping.getProperty();if (boundSql.hasAdditionalParameter(prop)) {newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));}}Object[] queryArgs = invocation.getArgs();queryArgs[0] = newMs;System.out.println("打印新SQL语句" + newSql);}}//继续执行逻辑return invocation.proceed();}private String getQueryConditionSqlScene(List<ParamClassInfo> paramClassInfos) {StringBuilder conditionParamBuilder = new StringBuilder();if (CollectionUtils.isEmpty(paramClassInfos)) {return "";}conditionParamBuilder.append(" WHERE ");int size = paramClassInfos.size();for (int index = 0; index < size; index++) {ParamClassInfo paramClassInfo = paramClassInfos.get(index);String keyName = paramClassInfo.getKeyName();//默认驼峰拆成下划线 ,比如 userName -》 user_name , name -> name//如果是需要取别名,其实可以加上自定义注解这些,但是本篇例子是轻封装,思路给到,你们i自己玩String underlineKeyName = camelToUnderline(keyName);conditionParamBuilder.append(underlineKeyName);Object keyValue = paramClassInfo.getKeyValue();String classType = paramClassInfo.getClassType();//其他类型怎么处理 ,可以按照类型区分 ,比如检测到一组开始时间,Date 拼接 between and等
// if (classType.equals("String")){
// conditionParamBuilder .append("=").append("\'").append(keyValue).append("\'");
// }conditionParamBuilder.append("=").append("\'").append(keyValue).append("\'");if (index != size - 1) {conditionParamBuilder.append(" AND ");}}return conditionParamBuilder.toString();}private static List<ParamClassInfo> convertParamList(Object obj) {List<ParamClassInfo> paramClassList = new ArrayList<>();for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(obj.getClass())) {if (!"class".equals(pd.getName())) {if (ReflectionUtils.invokeMethod(pd.getReadMethod(), obj) != null) {ParamClassInfo paramClassInfo = new ParamClassInfo();paramClassInfo.setKeyName(pd.getName());paramClassInfo.setKeyValue(ReflectionUtils.invokeMethod(pd.getReadMethod(), obj));paramClassInfo.setClassType(pd.getPropertyType().getSimpleName());paramClassList.add(paramClassInfo);}}}return paramClassList;}public static String camelToUnderline(String line){if(line==null||"".equals(line)){return "";}line=String.valueOf(line.charAt(0)).toUpperCase().concat(line.substring(1));StringBuffer sb=new StringBuffer();Pattern pattern= compile("[A-Z]([a-z\\d]+)?");Matcher matcher=pattern.matcher(line);while(matcher.find()){String word=matcher.group();sb.append(word.toUpperCase());sb.append(matcher.end()==line.length()?"":"_");}return sb.toString();}@Overridepublic Object plugin(Object o) {//获取代理权if (o instanceof Executor) {//如果是Executor(执行增删改查操作),则拦截下来return Plugin.wrap(o, this);} else {return o;}}/*** 定义一个内部辅助类,作用是包装 SQL*/class MyBoundSqlSqlSource implements SqlSource {private BoundSql boundSql;public MyBoundSqlSqlSource(BoundSql boundSql) {this.boundSql = boundSql;}@Overridepublic BoundSql getBoundSql(Object parameterObject) {return boundSql;}}private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {MappedStatement.Builder builder = newMappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());builder.resource(ms.getResource());builder.fetchSize(ms.getFetchSize());builder.statementType(ms.getStatementType());builder.keyGenerator(ms.getKeyGenerator());if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {builder.keyProperty(ms.getKeyProperties()[0]);}builder.timeout(ms.getTimeout());builder.parameterMap(ms.getParameterMap());builder.resultMaps(ms.getResultMaps());builder.resultSetType(ms.getResultSetType());builder.cache(ms.getCache());builder.flushCacheRequired(ms.isFlushCacheRequired());builder.useCache(ms.isUseCache());return builder.build();}@Overridepublic void setProperties(Properties properties) {//读取mybatis配置文件中属性}
代码简析:
驼峰转换下划线,用于转出数据库表的字段 :
通过反射把 sql入参的对象 不为空的属性名和对应的值,拿出来:
组件动态查询的sql 语句 :
写个简单测试用例:
@AutowiredDistrictMapper districtMapper;@Testpublic void test() {District query = new District();query.setCode("110000");query.setName("北京市");District district = districtMapper.queryOneJcDynamics(query);System.out.println(district.toString());District listQuery = new District();listQuery.setParentCode("110100");List<District> districts = districtMapper.queryListJcDynamics(listQuery);System.out.println(districts.toString());}
看下效果,可以看到都自动识别把不为空的字段属性和值拼接成查询条件了:
好了,该篇就到这。 抛砖引玉,领悟分步封装思路最重要,都去搞些小玩具娱乐娱乐吧。
相关文章:

Springboot 自定义 Mybatis拦截器,实现 动态查询条件SQL自动组装拼接(玩具)
前言 ps:最近在参与3100保卫战,战况很激烈,刚刚打完仗,来更新一下之前写了一半的博客。 该篇针对日常写查询的时候,那些动态条件sql 做个简单的封装,自动生成(抛砖引玉,搞个小玩具&a…...
Go 1.21新增的 slices 包详解(三)
Go 1.21新增的 slices 包提供了很多和切片相关的函数,可以用于任何类型的切片。 slices.Max 定义如下: func Max[S ~[]E, E cmp.Ordered](x S) E 返回 x 中的最大值,如果 x 为空,则 panic。对于浮点数 E, 如果有元素为 NaN&am…...

Python 在logging.config.dictConfig()日志配置方式下,使用自定义的Handler处理程序
文章目录 一、基于 RotatingFileHandler 的自定义处理程序二、基于 TimedRotatingFileHandler 的自定义处理程序 Python logging模块的基本使用、进阶使用详解 Python logging.handlers模块,RotatingFileHandler、TimedRotatingFileHandler 处理器各参数详细介绍 …...

Anaconda, Python, Jupyter和PyCharm介绍
目录 1 Anaconda, Python, Jupyter和PyCharm介绍 2 macOS通过Anaconda安装Python, Jupyter和PyCharm 3 使用终端创建虚拟环境并安装PyTorch 4 安装PyCharm并导入Anaconda虚拟环境 5 Windows操作系统下Anaconda与PyCharm安装 6 通过 Anaconda Navigator 创建 TensorFlow 虚…...

axios 各种方式的请求 示例
GET请求 示例一: 服务端代码 GetMapping("/f11") public String f11(Integer pageNum, Integer pageSize) {return pageNum " : " pageSize; }前端代码 <template><div class"home"><button click"getFun1…...

基于开源模型搭建实时人脸识别系统(四):人脸质量
续人脸识别实战之基于开源模型搭建实时人脸识别系统(三):人脸关键点、对齐模型概览与模型选型_CodingInCV的博客-CSDN博客 不论对于静态的人脸识别还是动态的人脸识别,我们都会面临一个问题,就是输入的人脸图像的质量可…...

【开发笔记】ubuntu部署指定版本的前后端运行环境(npm nodejs mysql)
目录 1 背景2 环境要求3 部署流程3.1 npm的安装3.2 nodejs的安装3.3 MySQL的安装 4 可能的问题 1 背景 在远程服务器上的Ubuntu系统中,部署指定版本的前后端项目的运行环境 2 环境要求 npm 9.5.1Nodejs v18.16.1MySQL 8.0.33 3 部署流程 3.1 npm的安装 通过安…...

用于优化开关性能的集成异质结二极管的4H-SiC沟道MOSFET
标题:4H-SiC Trench MOSFET with Integrated Heterojunction Diode for Optimizing Switching Performance 摘要 本研究提出了一种新型的4H-SiC沟道MOSFET,其在栅槽底部集成了异质结二极管(HJD-TMOS),并通过TCAD模拟进…...
优化个人博客总结
前面学习完怎么搭建个人博客,后面要做的就是排版优化自己的博客了,今天通过教程学习到了然后更爱美化其中的效果,还通过改写代码来带到基本的效果展示,同时也把最开始学习的计算速成课的笔记输出在上面,这也是一个很好…...

从零构建深度学习推理框架-9 再探Tensor类,算子输入输出的分配
再探Tensor类: 第二节中我们编写的Tensor类其实并不能满足我们的使用需要,我们将在这一节以代码阅读的方式来看看一个完全版本的Tensor应该具备怎样的要素,同时我们对Tensor类的分析来看看在C中一个设计好的类应该是怎么样的。 Tensor<fl…...
Vue使用element-ui
main.js配置 //引入Vue import Vue from vue //引入App import App from ./App.vue//完整引入 //引入ElementUI组件库 // import ElementUI from element-ui; //引入ElementUI全部样式 // import element-ui/lib/theme-chalk/index.css;//按需引入 import { Button,Row,DatePi…...
使用ApplicationRunner简化Spring Boot应用程序的初始化和启动
ApplicationRunner这个接口,我们一起来了解这个组件,并简单使用它吧。🤭 引言 在开发Spring Boot应用程序时,应用程序的初始化和启动是一个重要的环节。ApplicationRunner是Spring Boot提供的一个有用的接口,可以帮助…...

Vue 2.x 项目升级到 Vue 3详细指南【修改清单】
文章目录 前言0.迁移过程1. 安装 Vue 32. 逐一处理迁移中的警告3. 迁移全局和内部 API4. 迁移 Vue Router 和 Vuex5. 处理其他的不兼容变更 1. Vue3特性1. Composition API2. 更好的性能3. 更好的 TypeScript 支持4. 多个根元素5. Suspense 组件6. Teleport 组件7. 全局 API 的…...

【算法日志】贪心算法刷题:重叠区问题(day31)
代码随想录刷题60Day 目录 前言 无重叠区间(筛选区间) 划分字母区间(切割区间) 合并区间 前言 今日的重点是掌握重叠区问题。 无重叠区间(筛选区间) int eraseOverlapIntervals(vector<vector<in…...

基于Jenkins构建生产CICD环境、jenkins安装
目录 Jenkins简介 安装配置Jenkins Jenkins简介 Jenkins是一个用Java编写的开源的持续集成工具。在与Oracle发生争执后,项目从Hudson项目独立。官方网站:https://jenkins.io/。 Jenkins提供了软件开发的持续集成服务。它运行在Servlet容器中ÿ…...

基于Java SpringBoot+vue+html 的地方美食系统(2.0版本)
博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W,csdn、博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 1 简介2 技术栈3 系统流程的分析3.1 用户管理的流程3.2个人中心管理流程3.3登录流程 4系统设计…...

opencv-gpu版本编译(添加java支持,可选)实现硬解码
目录 opencv gpu版本编译,实现硬解码,加速rtsp视频流读取1、准备文件2、复制 NVCUVID 头文件到 cuda 安装目录 include3、安装相关依赖4、 执行cmake5、编译安装6、测试 opencv gpu版本编译,实现硬解码,加速rtsp视频流读取 前置条…...

数据分析问答总结
一、SQL窗口函数 1.是什么 OLAP(Online Anallytical Processing联机分析处理),对数据库数据进行实时分析处理。 2.基本语法: <窗口函数>OVER (PARTITION BY <用于分组的列名> ORDER BY <用于排序的…...

Python学习笔记_实战篇(二)_django多条件筛选搜索
多条件搜索在很多网站上都有用到,比如京东,淘宝,51cto,等等好多购物教育网站上都有,当然网上也有很多开源的比楼主写的好的多了去了,仅供参考,哈哈 先来一张效果图吧,不然幻想不出来…...

【生态经济学】利用R语言进行经济学研究技术——从数据的收集与清洗、综合建模评价、数据的分析与可视化、因果推断等方面入手
查看原文>>>如何快速掌握利用R语言进行经济学研究技术——从数据的收集与清洗、综合建模评价、数据的分析与可视化、因果推断等方面入手 近年来,人工智能领域已经取得突破性进展,对经济社会各个领域都产生了重大影响,结合了统计学、…...

XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...

大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...

(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...

srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...
Vite中定义@软链接
在webpack中可以直接通过符号表示src路径,但是vite中默认不可以。 如何实现: vite中提供了resolve.alias:通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...
Spring Boot + MyBatis 集成支付宝支付流程
Spring Boot MyBatis 集成支付宝支付流程 核心流程 商户系统生成订单调用支付宝创建预支付订单用户跳转支付宝完成支付支付宝异步通知支付结果商户处理支付结果更新订单状态支付宝同步跳转回商户页面 代码实现示例(电脑网站支付) 1. 添加依赖 <!…...