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…...
宝塔部署Java+Vue前后端分离项目经验总结
前言 之前部署服务器都是在Linux环境下自己一点一点安装软件,听说用宝塔傻瓜式部署更快,这次浅浅尝试了一把。 确实简单! 1、 买服务器 咋买服务器略,记得服务器装系统就装 Cent OS 7系列即可,我装的7.6。 2、创建…...
【公告】停止更新
CSDN 博客的限制太多了。阅读体验也非常差。后续将不再 CSDN 上更新。 逐步迁移到掘金和个人博客。 欢迎关注 掘金:0xforee 个人博客:0xforee’s blog...
AutoHotKey+VSCode开发扩展推荐
原来一直用的大众推荐的SciTeAHK版,最近发现VSCode更舒服一些,有几个必装的扩展推荐一下: AutoHotkey Plus 请注意不是AutoHotkey Plus Plus。如果在扩展商店里搜索会有两个,一个是Plus,一个是Plus Plus。我选择Pllus&…...
了解 JSON 格式
一、JSON 基础 JSON(JavaScript Object Notation,JavaScript 对象表示法)是一种轻量级的数据交换格式,JSON 的设计目的是使得数据的存储和交换变得简单。 JSON 易于人的阅读和书写,同时也易于机器的解析和生成。尽管 J…...
[RDMA] 高性能异步的消息传递和RPC :Accelio
1. Introduce Accelio是一个高性能异步的可靠消息传递和RPC库,能优化硬件加速。 RDMA和TCP / IP传输被实现,并且其他的传输也能被实现,如共享存储器可以利用这个高效和方便的API的优点。Accelio 是 Mellanox 公司的RDMA中间件,用…...
typescript报错:‘name‘ was also declared here
问题再现 用 Typescript 时, 遇到一个声明常量 name 的报错。代码如下: let name:string"zhangsan"; let num:number1001;执行编译时报错: 原因 在默认状态下,typescript 将 DOM typings 作为全局的运行环境&#…...
第十章:联邦学习视觉案例
代码 传送门...
c语言——输出一个整数的所有因数
//输出一个整数的所有因数 #include<stdio.h> #include<stdlib.h> int main() {int number,i;printf("输入整数:");scanf("%d",&number);printf(" %d 的因数有: ",number);for(i1;i<number;i){if(numb…...
mqtt学习记录
目录 1 匿名登录2 ⽤户名密码登录,配置接收的主题mosquitto 配置文件修改添加⽤户信息添加topic和⽤户的关系登录演示 3 遗嘱机制 1 匿名登录 ⾸先打开三个终端, 启动代理服务:mosquitto -v -v 详细模式 打印调试信息 默认占⽤:…...
爬虫逆向实战(十八)--某得科技登录
一、数据接口分析 主页地址:某得科技 1、抓包 通过抓包可以发现数据接口是AjaxLogin 2、判断是否有加密参数 请求参数是否加密? 查看“载荷”模块可以发现有一个password加密参数和一个__RequestVerificationToken 请求头是否加密? 无…...
Java-数组
什么是数组 数组:可以看成是相同类型元素的一个集合。在内存中是一段连续的空间。 在java中, 数组中存放的元素其类型相同数组的空间是连在一起的每个空间有自己的编号,起始位置的编号为0,即数组的下标。 数组的创建及初始化 数…...
Dart 入门Hello world
1、下载Dart sdk IntelliJ & Android Studio | Dart 2、安装Dart 插件 3、安装后重启IDEA,创建Dart项目 4、创建dart文件 5、编写函数: void main() {print("Hello world"); } 6、运行: 官网学习:Dart 语言开发文…...
HTML是什么?
HTML是什么? 超文本标记语言(英语:HyperText Markup Language,简称:HTML)是一种用于创建网页的标准标记语言。 您可以使用 HTML 来建立自己的 WEB 站点,HTML 运行在浏览器上,由浏览器…...
【UniApp开发小程序】商品详情展示+评论、评论展示、评论点赞+商品收藏【后端基于若依管理系统开发】
文章目录 界面效果界面实现工具js页面日期格式化 后端收藏ControllerServicemapper 评论ControllerServiceMapper 商品Controller 阅读Service 界面效果 【说明】 界面中商品的图片来源于闲鱼,若侵权请联系删除 【商品详情】 【评论】 界面实现 工具js 该工…...
rabbitMq安装后无法启动可视化页面http://localhost:15672处理
本次安装环境信息: 系统:win10 64位专业版 erlang:otp_win64_23.0 rabbitMQ:rabbitmq-server-3.8.5 安装rabbitMQ需要依赖erlang语言环境,所以需要我们下载erlang的环境安装程序。 一、下载安装程序 rabbitMQ安装…...
材料行业可以转IC设计后端吗?
近来有许多材料行业的小伙伴通过后台来问我对于职业规划的看法,甚至有些小伙伴直接点明了某个行业适不适合自己,那么我这边仅以近年来比较热门的数字芯片设计来展开讲讲,材料适不适合转行做IC呢。 对于理工科的同学而言,选择哪个…...
vue3 基础知识
vue3创建一个项目 PS D:\code> npm init vuelatestVue.js - The Progressive JavaScript Framework√ Add TypeScript? ... No / Yes √ Add JSX Support? ... No / Yes √ Add Vue Router for Single Page Application development? ... No / Yes √ Add Pinia for sta…...
【线性代数-3Blue1Brown】- 2 线性组合、张成的空间与基
飞书原文链接:Docs...
Kafka—工作流程、如何保证消息可靠性
什么是kafka? 分布式事件流平台。希望不仅仅是存储数据,还能够数据存储、数据分析、数据集成等功能。消息队列(把数据从一方发给另一方),消息生产好了但是消费方不一定准备好了(读写不一致)&am…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...
AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别
【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而,传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案,能够实现大范围覆盖并远程采集数据。尽管具备这些优势…...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...
