MyBatis实现动态SQL更新

博主记得在一个周五快下班的下午,产品找到我(为什么总感觉周五快下班就来活 😂),跟我说有几个业务列表查询需要加上时间条件过滤数据,这个条件可能会变,不保证以后不修改,这个改动涉及到多个列表查询,于是博主思考了一会想了几种实现方案,

  1. 最简单,直接将时间条件写死,由 Service 层传递给 Dao 层进行条件拼接。实现上虽然简单,但是代码上感觉非常 low,如果这个参数需要在很多方法里进行传递,那么工作量就比较大。
  2. 复杂一点,通过 MyBatis 的拦截器机制,在 SQL 拼接的 prepare 阶段修改 SQL 语句,实现动态 SQL。

考虑到拦截器机制不需要修改过多代码,因此本文博主将带领大家学习如何利用 MyBatis 拦截器机制来优雅的实现这个需求。

本文示例代码全部在 Spring Boot3.0、Mybatis Plus3.5.3.1 版本下运行。

简介

MyBatis 是一个流行的 Java 持久层框架,它提供了灵活的 SQL 映射和执行功能。有时候我们可能需要在运行时动态地修改 SQL 语句,例如添加一些条件、排序、分页等。MyBatis 提供了一个强大的机制来实现这个需求,那就是拦截器(Interceptor)。

推荐博主开源的 H5 商城项目waynboot-mall,这是一套全部开源的微商城项目,包含三个项目:运营后台、H5 商城前台和服务端接口。实现了商城所需的首页展示、商品分类、商品详情、商品 sku、分词搜索、购物车、结算下单、支付宝/微信支付、收单评论以及完善的后台管理等一系列功能。 技术上基于最新得 Springboot3.0、jdk17,整合了 MySql、Redis、RabbitMQ、ElasticSearch 等常用中间件。分模块设计、简洁易维护,欢迎大家点个 star、关注博主。

github 地址:https://github.com/wayn111/waynboot-mall

拦截器介绍

拦截器是一种基于 AOP(面向切面编程)的技术,它可以在目标对象的方法执行前后插入自定义的逻辑。MyBatis 定义了四种类型的拦截器,分别是:

  • Executor:拦截执行器的方法,例如 update、query、commit、rollback 等。可以用来实现缓存、事务、分页等功能。
  • ParameterHandler:拦截参数处理器的方法,例如 setParameters 等。可以用来转换或加密参数等功能。
  • ResultSetHandler:拦截结果集处理器的方法,例如 handleResultSets、handleOutputParameters 等。可以用来转换或过滤结果集等功能。
  • StatementHandler:拦截语句处理器的方法,例如 prepare、parameterize、batch、update、query 等。可以用来修改 SQL 语句、添加参数、记录日志等功能。

实现拦截器

  1. 定义一个实现 org.apache.ibatis.plugin.Interceptor 接口的拦截器类,并重写其中的 intercept、plugin 和 setProperties 方法。
  2. 添加 @Intercepts 注解,写上需要拦截的对象和方法,以及方法参数,例如 @Intercepts({@Signature(type = StatementHandler.class, method = “prepare”, args = {Connection.class, Integer.class})}),表示在 SQL 执行之前进行拦截处理。

注册拦截器

Spring Boot 项目中集成了 Mybatis Plus 后要让拦截器生效很简单,Mybatis Plus 的自动配置类会读取项目中所有注册到 Spring 容器的拦截器并进行自动注册。如下图,

MybatisPlusAutoConfiguration

注册拦截器

所以我们只需要定义一个 DynamicSqlInterceptor 拦截器并加上 @Component 注解就行,代码如下,

@Component
@Slf4j
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class DynamicSqlInterceptor implements Interceptor {...
}

代码示例

yml 配置

指定 xml 文件中需要替换的占位符标识:@dynamicSql 以及待替换日期条件。

# 动态sql配置
dynamicSql:placeholder: "@dynamicSql"date: "2023-07-10 20:10:30"

Dao 层代码

在需要进行 SQL 占位符替换的方法上加 @DynamicSql 注解。

public interface DynamicSqlMapper {@DynamicSqlLong count();
}

mapper 文件

将日期条件改成占位符 where create_time > @dynamicSql

<mapper namespace="ltd.newbee.mall.core.dao.DynamicSqlMapper"><select id="count" resultType="java.lang.Long">select count(1) from memberwhere create_time > @dynamicSql</select>
</mapper>

拦截器核心代码

@Component
@Slf4j
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class DynamicSqlInterceptor implements Interceptor {@Value("${dynamicSql.placeholder}")private String placeholder;@Value("${dynamicSql.date}")private  String dynamicDate;@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 1. 获取 StatementHandler 对象也就是执行语句StatementHandler statementHandler = (StatementHandler) invocation.getTarget();// 2. MetaObject 是 MyBatis 提供的一个反射帮助类,可以优雅访问对象的属性,这里是对 statementHandler 对象进行反射处理,MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY,SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,new DefaultReflectorFactory());// 3. 通过 metaObject 反射获取 statementHandler 对象的成员变量 mappedStatementMappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");// mappedStatement 对象的 id 方法返回执行的 mapper 方法的全路径名,如ltd.newbee.mall.core.dao.UserMapper.insertUserString id = mappedStatement.getId();// 4. 通过 id 获取到 Dao 层类的全限定名称,然后反射获取 Class 对象Class<?> classType = Class.forName(id.substring(0, id.lastIndexOf(".")));// 5. 获取包含原始 sql 语句的 BoundSql 对象BoundSql boundSql = statementHandler.getBoundSql();String sql = boundSql.getSql();log.info("替换前---sql:{}", sql);// 拦截方法String mSql = null;// 6. 遍历 Dao 层类的方法for (Method method : classType.getMethods()) {// 7. 判断方法上是否有 DynamicSql 注解,有的话,就认为需要进行 sql 替换if (method.isAnnotationPresent(DynamicSql.class)) {mSql = sql.replaceAll(placeholder, String.format("'%s'", dynamicDate));break;}}if (StringUtils.isNotBlank(mSql)) {log.info("替换后---mSql:{}", mSql);// 8. 对 BoundSql 对象通过反射修改 SQL 语句。Field field = boundSql.getClass().getDeclaredField("sql");field.setAccessible(true);field.set(boundSql, mSql);}// 9. 执行修改后的 SQL 语句。return invocation.proceed();}@Overridepublic Object plugin(Object target) {// 使用 Plugin.wrap 方法生成代理对象return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {// 获取配置文件中的属性值}
}

现在我们对拦截器核心代码逻辑进行讲解:

  1. 通过 invocation 参数获取 statementHandler 对象,也就是包含拼接后 SQL 语句的对象。
  2. 获取 metaObject 对象, MetaObject 是 MyBatis 提供的一个反射帮助类,可以优雅访问对象的属性,这里是访问 statementHandler 对象进行反射处理。
  3. 通过 metaObject 反射获取 statementHandler 对象的成员变量 mappedStatement。
  4. 通过 mappedStatement 对象的 id 方法获取到 Dao 层类的全限定名称,然后反射获取 Dao 层类的 Class 对象。
  5. 获取包含原始 SQL 语句的 BoundSql 对象。
  6. 遍历 Dao 层类的方法。
  7. 判断方法上是否有 DynamicSql 注解,有的话就进行时间条件替换
  8. 对 BoundSql 对象通过反射修改 SQL 语句。
  9. 执行修改后的 SQL 语句。

代码测试

// 测试类
@SpringBootTest
@RunWith(SpringRunner.class)
public class DynamicTest {@Autowiredprivate DynamicSqlMapper dynamicSqlMapper;@Testpublic void test() {Long count = dynamicSqlMapper.count();Assert.notNull(count, "count不能为null");}
}

执行结果:

2023-07-11 22:13:33.375 [main] INFO  l.n.m.config.DynamicSqlInterceptor - [intercept,52] - 替换前---sql:select count(1) from memberwhere create_time > @dynamicSql
2023-07-11 22:13:33.376 [main] INFO  l.n.m.config.DynamicSqlInterceptor - [intercept,62] - 替换后---mSql:select count(1) from memberwhere create_time > '2023-07-10 20:10:30'

拦截器应用场景

  • SQL 语句执行监控:可以拦截执行的 SQL 方法,打印执行的 SQL 语句、参数等信息,并且还能够记录执行的总耗时,可供后期的 SQL 分析时使用。
  • SQL 分页查询:MyBatis 中使用的 RowBounds 使用的内存分页,在分页前会查询所有符合条件的数据,在数据量大的情况下性能较差。通过拦截器,可以在查询前修改 SQL 语句,提前加上需要的分页参数。
  • 公共字段的赋值:在数据库中通常会有 createTime , updateTime 等公共字段,这类字段可以通过拦截统一对参数进行的赋值,从而省去手工通过 set 方法赋值的繁琐过程。
  • 数据权限过滤:在很多系统中,不同的用户可能拥有不同的数据访问权限,例如在多租户的系统中,要做到租户间的数据隔离,每个租户只能访问到自己的数据,通过拦截器改写 SQL 语句及参数,能够实现对数据的自动过滤。
  • SQL 语句替换:对 SQL 中条件或者特殊字符进行逻辑替换。(也是本文的应用场景)

总结

到此本文讲解的 MyBatis 实现动态 SQL 内容就讲解完毕了,希望大家喜欢。

关注公众号【waynblog】每周分享技术干货、开源项目、实战经验、高效开发工具等,您的关注将是我的更新动力!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/18528.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Grafana 图形面板定制方案

Grafana 在一个 Panel 中添加多数据源同时展示以及修改通过 transform 修改图表图例的方式。 多个数据在一个折线图中 在 Grafana 中我们可能会希望多个数据在一个Panel 中展示&#xff0c;比如&#xff1a; 通过编辑 Panel 增加 Query 数据我们即可做到&#xff1a;像上面中…

windows开机启动nginx(服务方式启动)

提示&#xff1a;本文章介绍如何借助Windows Service Wrapper小工具&#xff0c;将Nginx转换为Windows服务&#xff0c;在服务中心配置自启动&#xff0c;从而在开机时windows自行启动Nginx服务 Nginx是什么 官方链接&#xff1a;nginx下载 Nginx 是一个高性能的HTTP和反向代理…

大学生活动社交小程序开发笔记(1)

可研分析 大学生活动社交小程序是一种基于移动互联网的社交平台&#xff0c;旨在为大学生提供一个方便、快捷、安全的社交和活动交流平台 功能规划 活动发布&#xff1a;平台可以发布将要举行的活动&#xff0c;包括时间、地点、费用等信息&#xff0c;并邀请其他用户参加。…

Graphics Mill 11.1.18 -24-06-2023 Crack

Graphics Mill 是适用于 .NET 和 ASP.NET 开发人员的最强大的成像工具集。它允许用户轻松向 .NET 应用程序添加复杂的光栅和矢量图像处理功能。 加载和保存 JPEG、PNG 和另外 8 种图像格式 调整大小、裁剪、自动修复、色度键和 30 多种其他图像操作 可处理任何尺寸&#xff08…

Kkfileview | Docker | +Redis文件预览kkfile配置

文章目录 简介DockerRedis部署 简介 kkFileView为文件文档在线预览解决方案&#xff0c;该项目使用流行的spring boot搭建&#xff0c;易上手和部署&#xff0c;基本支持主流办公文档的在线预览&#xff0c;如doc,docx,xls,xlsx,ppt,pptx,pdf,txt,zip,rar,图片,视频,音频等等 …

WebSocket使用记录

使用视频地址 1、添加前端使用文件 2、后端配置 2.1添加依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>2.2添加websocket配置类 import org.spri…

Android mac 交叉编译与ffmpeg编译踩坑记 (v7a 与 v8a and 动态库与静态库)

Android mac 交叉编译与ffmpeg编译踩坑记 环境: system: mac NDK: android-ndk-r17c Fffmpeg: ffmpeg-4.0.2 Cmake: 3.10.2 Gradle: 4.1.3 tips: 本文记录踩坑过程,具体细节如果感兴趣可以在评论区留言交流讨论! mac 编译 (动态库(so)) 首先来回顾一下,mac原始库是如何…

沉浸式翻译(immersive-translate):解决谷歌翻译无法使用的问题

前言&#xff1a; 在如今的全球化时代&#xff0c;语言不再是隔离人们交流的障碍。然而&#xff0c;在浏览外语网页时&#xff0c;我们常常遇到一个问题&#xff1a;无法准确理解其中的内容。 谷歌翻译是一款强大的翻译工具&#xff0c;但由于各种原因&#xff0c;我们可能无法…

负载均衡的知识点

目录 1.负载均衡是什么 2.负载均衡的分类 客户端负载均衡&#xff1a; 服务端负载均衡&#xff1a; 软件实现&#xff1a;根据OSI模型可以分为四层负载均衡和七层负载均衡 硬件实现&#xff1a; 附1&#xff1a;客户端和服务端&#xff1a; 附2&#xff1a;OSI…

nginx基本使用

这是一份完整的nginx配置文件&#xff1a; #user nobody; worker_processes 1;#error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info;#pid logs/nginx.pid;events {worker_connections 1024; }http {include mi…

Markdown基本用法

目录 1 字体倾斜 1.1 加* 1.2 加_ 2 字体加粗 2.1 加** 2.2 加__ 3 字体上带删除线 4 文字变标题 5 超链接 5.1 直接输入地址 5.2 将超链接改成文字 5.2.1 同行写法 5.2.2 不同行写法 6 文字前加 6.1 号 6.2 *号 6.3 -号 7 有序列表 8 …

2023数学建模国赛常用算法-Topsis优劣解距离法

更多国赛数学建模资料思路&#xff0c;关注文末&#xff01; 1 优劣解距离法&#xff08;TOPSIS&#xff09;简介 1.1 概念 TOPSIS 法是一种常用的组内综合评价方法&#xff0c;能充分利用原始数据的信息&#xff0c;其结果能精确地反映各评价方案之间的差距。基本过程为基于…