MyBatis插件原理及应用
🎮 作者主页:点击
🎁 完整专栏和代码:点击
🏡 博客主页:点击
文章目录
- 介绍
- <plugins>标签解析
- 拦截器链的工作原理
- 插件的应用场景
- MyBatis插件应用的四个组件
- InterceptorChain和Interceptor
MyBatis框架允许用户通过自定义拦截器的方式改变SQL的执行行为,例如在SQL执行时追加SQL分页语法,从而达到简化分页查询的目的。用户自定义的拦截器也被称为MyBatis插件。
介绍
在MyBatis主配置文件中,可以通过标签注册用户自定义的插件信息,例如:
<plugins><plugin interceptor="com.zy.client.plugin.ExamplePlugin"><property name="myProp" value="1000"/></plugin></plugins>
MyBatis的插件实际上就是一个拦截器,Configuration类中维护了一个InterceptorChain的实例:
protected final InterceptorChain interceptorChain = new InterceptorChain();public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);return parameterHandler;}public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,ResultHandler resultHandler, BoundSql boundSql) {ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);return resultSetHandler;}public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Executor executor;if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {executor = new SimpleExecutor(this, transaction);}// 如果允许缓存,默认是开启的,对executor进行包装代理if (cacheEnabled) {executor = new CachingExecutor(executor);}// 拦截器插件executor = (Executor) interceptorChain.pluginAll(executor);return executor;}public void addInterceptor(Interceptor interceptor) {interceptorChain.addInterceptor(interceptor);}
interceptorChain属性是一个拦截器链,用于存放通过标签注册的所有拦截器,Configration类中还定义了一个addInterceptor()方法,用于向拦截器链中添加拦截器.
标签解析
MyBatis框架在应用启动时会对标签进行解析。MyBatis 的 XMLConfigBuilder 类是用来解析 MyBatis 配置文件的核心组件之一,其中 pluginElement() 方法的主要功能是解析 标签,并将其中定义的插件实例化后添加到 MyBatis 配置中,形成插件链。
org.apache.ibatis.builder.xml.XMLConfigBuilder#pluginElement
private void pluginElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {String interceptor = child.getStringAttribute("interceptor");Properties properties = child.getChildrenAsProperties();Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();interceptorInstance.setProperties(properties);configuration.addInterceptor(interceptorInstance);}}}
方法会首先通过 MyBatis 的 XPathParser 提取 节点的所有子节点。
【1】调用 parent.getChildren() 方法,提取 标签下的所有 子节点。
【2】对每个 节点,通过 child.getStringAttribute(“interceptor”) 提取插件的全限定类名,如 org.mybatis.example.MyInterceptor。
【3】调用 child.getChildrenAsProperties() 提取 节点下的 子节点,将其转化为一个 Properties 对象。
【4】通过resolveClass(interceptor).getDeclaredConstructor().newInstance();对配置的插件进行实例化对象。调用 resolveClass(interceptor) 将类名转化为 Class 对象,并通过反射调用其无参构造方法实例化插件对象。
【5】调用 setProperties(Properties) 方法,将解析的属性注入到插件实例中。Interceptor 接口中定义了 setProperties(Properties properties) 方法,插件需要实现该方法以接收参数。
【6】调用 configuration.addInterceptor(interceptorInstance) 将插件实例添加到 Configuration 的插件集合中,等待后续被拦截器链加载。
拦截器链的工作原理
MyBatis 的插件采用责任链模式,所有插件都会包装成拦截器链。在实际执行 SQL 操作时,MyBatis 会依次执行这些拦截器的 intercept 方法。
插件接口定义
所有插件都需要实现 Interceptor 接口,该接口定义如下:
public interface Interceptor {Object intercept(Invocation invocation) throws Throwable;default Object plugin(Object target) {return Plugin.wrap(target, this);}default void setProperties(Properties properties) {// 可以由子类实现,接收插件的配置参数}
}
plugin() 方法通过 Plugin.wrap 将目标对象动态代理为被插件管理的对象,从而实现拦截。
插件的应用场景
通过拦截器链,开发者可以在以下场景中插入自定义逻辑:
- SQL 日志记录
拦截 Executor.query 或 Executor.update 方法,记录 SQL 执行时间和参数。 - 性能监控
在方法调用前后记录时间,用于性能分析。 - 权限控制
检查 SQL 是否符合权限要求。 - 动态 SQL 修改
在拦截器中对 SQL 进行动态修改或优化。
MyBatis插件应用的四个组件
用户自定义的插件只能对MyBatis中的4种组件的方法进行拦截,这4种组件及方法如下:
Executor(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler(getParameterObject, setParameters)
ResultSetHandler(handleResultSets, handleOutputParameters)
StatementHandler(prepare, parameterize, batch, update, query)
为什么MyBatis插件能够对Executor、ParameterHandler、ResultSetHandler、StatementHandler四种组件的实例进行拦截呢?
我们可以从MyBatis源码中获取答案。前面在介绍Configuration组件的作用时,我们了解到Configuration组件有3个作用,分别如下:
(1)用于描述MyBatis配置信息,项目启动时,MyBatis的所有配置信息都被转换为Configuration对象。
(2)作为中介者简化MyBatis各个组件之间的交互,解决了各个组件错综复杂的调用关系,属于中介者模式的应用。
(3)作为Executor、ParameterHandler、ResultSetHandler、StatementHandler组件的工厂创建这些组件的实例。
MyBatis使用工厂方法创建Executor、ParameterHandler、ResultSetHandler、StatementHandler组件的实例,其中一个原因是可以根据用户配置的参数创建不同实现类的实例;另一个比较重要的原因是可以在工厂方法中执行拦截逻辑。我们不妨看一下Configuration类中这些工厂方法的实现.
在Configuration类的newParameterHandler()、newResultSetHandler()、newStatementHandler()、newExecutor()这些工厂方法中,都调用了InterceptorChain对象的pluginAll()方法,pluginAll()方法返回ParameterHandler、ResultSetHandler、StatementHandler或者Executor对象的代理对象,拦截逻辑都是在代理对象中完成的。这就解释了为什么MyBatis自定义插件只能对Executor、ParameterHandler、ResultSetHandler、StatementHandler这4种组件的方法进行拦截。
InterceptorChain和Interceptor
在InterceptorChain类中通过一个List对象维护所有的拦截器实例,在InterceptorChain的pluginAll()方法中,会调用所有拦截器实例的plugin()方法,该方法返回一个目标对象的代理对象。
Interceptor接口中定义了3个方法,intercept()方法用于定义拦截逻辑,该方法会在目标方法调用时执行。plugin()方法用于创建Executor、ParameterHandler、ResultSetHandler或StatementHandler的代理对象,该方法的参数即为Executor、ParameterHandler、ResultSetHandler或StatementHandler组件的实例。setProperties()方法用于设置插件的属性值。需要注意的是,intercept()接收一个Invocation对象作为参数,Invocation对象中封装了目标对象的方法及参数信息。Invocation类的实现代码如下:
public class Invocation {private final Object target;private final Method method;private final Object[] args;public Invocation(Object target, Method method, Object[] args) {this.target = target;this.method = method;this.args = args;}public Object getTarget() {return target;}public Method getMethod() {return method;}public Object[] getArgs() {return args;}public Object proceed() throws InvocationTargetException, IllegalAccessException {return method.invoke(target, args);}}
Invocation类中封装了目标对象、目标方法及参数信息,我们可以通过Invocation对象获取目标对象(Executor、ParameterHandler、ResultSetHandler或StatementHandler)的所有信息。另外,Invocation类中提供了一个proceed()方法,该方法用于执行目标方法的逻辑。所以在自定义插件类中,拦截逻辑执行完毕后一般都需要调用proceed()方法执行目标方法的原有逻辑。
为了便于用户创建Executor、ParameterHandler、ResultSetHandler或StatementHandler实例的代理对象,MyBatis中提供了一个Plugin工具类
Plugin类实现了InvocationHandler接口,即采用JDK内置的动态代理方式创建代理对象。Plugin类中维护了Executor、ParameterHandler、ResultSetHandler或者StatementHandler类的实例,以及用户自定义的拦截器实例和拦截器中通过Intercepts注解指定的拦截方法。Plugin类的invoke()方法会在调用目标对象的方法时执行,在invoke()方法中首先判断该方法是否被Intercepts注解指定为被拦截的方法,如果是,则调用用户自定义拦截器的intercept()方法,并把目标方法信息封装成Invocation对象作为intercept()方法的参数。Plugin类中还提供了一个静态的wrap()方法,该方法用于简化动态代理对象的创建。
public static Object wrap(Object target, Interceptor interceptor) {Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);Class<?> type = target.getClass();Class<?>[] interfaces = getAllInterfaces(type, signatureMap);if (interfaces.length > 0) {return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}return target;}
wrap()方法的第一个参数为目标对象,即Executor、ParameterHandler、ResultSetHandler、StatementHandler类的实例;第二个参数为拦截器实例。在wrap()方法中首先调用getSignatureMap()方法获取Intercepts注解指定的要拦截的组件及方法,然后调用getAllInterfaces()方法获取当前Intercepts注解指定要拦截的组件的接口信息,接着调用Proxy类的静态方法newProxyInstance()创建一个动态代理对象。
Intercepts注解用于修饰拦截器类,告诉拦截器要对哪些组件的方法进行拦截。
相关文章:

MyBatis插件原理及应用
🎮 作者主页:点击 🎁 完整专栏和代码:点击 🏡 博客主页:点击 文章目录 介绍<plugins>标签解析拦截器链的工作原理插件的应用场景MyBatis插件应用的四个组件InterceptorChain和Interceptor MyBatis框架…...
[M最短路] lc743. 网络延迟时间(spfa最短路+单源最短路)
文章目录 1. 题目来源2. 题目解析 1. 题目来源 链接:743. 网络延迟时间 相关链接: [图最短路模板] 五大最短路常用模板) 2. 题目解析 怎么讲呢,挺抽象的…很久没写最短路算法了。反正也是写出来了,但脱离了模板,把…...
MySQL 中的锁
MySQL 中的锁:全面解析与应用指南 在 MySQL 数据库的复杂世界里,锁是确保数据一致性、完整性以及并发控制的关键机制。无论是简单的小型应用还是复杂的企业级系统,深入理解 MySQL 中的锁对于优化数据库性能、避免数据冲突和错误都具有至关重要…...

【动手学电机驱动】STM32-FOC(8)MCSDK Profiler 电机参数辨识
STM32-FOC(1)STM32 电机控制的软件开发环境 STM32-FOC(2)STM32 导入和创建项目 STM32-FOC(3)STM32 三路互补 PWM 输出 STM32-FOC(4)IHM03 电机控制套件介绍 STM32-FOC(5&…...

【C++11】尽显锋芒
(续) 一、可变参数模板 C11支持可变参数模板,也就是说支持可变数量参数的函数模板和类模板,可变数目的参数被称 为参数包,存在两种参数包:模板参数包,表示零或多个模板参数;函数参数包:表示零…...
掌握控制流的艺术:Go语言中的if、for和switch语句
标题:掌握控制流的艺术:Go语言中的if、for和switch语句 在Go语言的编程世界中,控制流语句是构建程序逻辑的基石。if语句、for循环和switch语句是我们最常用的控制流工具,它们让我们能够根据不同的条件执行不同的代码块。本文将深入探讨这些语句的使用方法、技术细节和实际…...

飞书会话消息左右排列
飞书会话消息左右排列 1. 飞书登录后,点击头像,弹出菜单有个按钮设置 2. 3....
.net 支持跨平台(桌面)系列技术汇总
1. 首先微软老大哥的.net core 。 .NET Core 是微软开发的一个跨平台、高性能的开源框架,用于构建云和互联网连接的新型应用。 它允许开发者在 Windows、macOS 和 Linux 上使用喜爱的开发工具进行开发,并支持部署到云或本地环境。 .NET Core 是对 .NET …...
springboot 静态资源访问
最近在学习springboot,在学习中一个静态资源访问,难道了我三天,在网上找了很多的资料,又是配置,又是重写WebMvcConfigurationSupport,因为以前没有接触,本来很简单的事情走了很多弯路࿰…...

【linux学习指南】初识Linux进程信号与使用
文章目录 📝信号快速认识📶⽣活⻆度的信号📶 技术应⽤⻆度的信号🌉 前台进程(键盘)🌉⼀个系统函数 📶信号概念📶查看信号 🌠 信号处理🌉 忽略此信…...

L1G1000 书生大模型全链路开源开放体系笔记
关卡任务 观看本关卡视频后,写一篇关于书生大模型全链路开源开放体系的笔记。 视频链接:【书生浦语大模型全链路开源体系】 : 书生浦语大模型开源开放体系_哔哩哔哩_bilibili 书生大模型全链路开源开放体系笔记 在人工智能领域,大模型的…...

亚信安全与飞书达成深度合作
近日,亚信安全联合飞书举办的“走近先进”系列活动正式走进亚信。活动以“安全护航信息化 共筑数字未来路”为主题,吸引了众多数字化转型前沿企业的近百位领导参会。作为“走近先进”系列的第二场活动,本场活动更加深入挖掘了数字化转型的基础…...
深入讲解Spring Boot和Spring Cloud,外加图书管理系统实战!
很抱歉,我的疏忽,说了这么久还没有给大家详细讲解过Spring Boot和Spring Cloud,那今天给大家详细讲解一下。 大家可以和下面这三篇博客一起看: 1、Spring Boot 和 Spring Cloud 微服务开发实践详解https://blog.csdn.net/speaking_me/artic…...

【三维生成】Edify 3D:可扩展的高质量的3D资产生成(英伟达)
标题:Edify 3D: Scalable High-Quality 3D Asset Generation 项目:https://research.nvidia.com/labs/dir/edify-3d demo:https://build.nvidia.com/Shutterstock/edify-3d 文章目录 摘要一、前言二、多视图扩散模型2.1.消融研究 三、重建模型…...
Java求职招聘网站开发实践
一、项目介绍 本文将介绍如何使用Java技术栈开发一个求职招聘网站。该网站主要实现求职者和招聘方的双向选择功能,包含用户管理、职位发布、简历投递等核心功能。 二、技术选型 后端框架:Spring Boot 2.7.0数据库:MySQL 8.0前端框架&#…...
一文详细了解websocket应用以及连接断开的解决方案
文章目录 websocketvite 热启动探索websocket -心跳websocket 事件监听应用过程中问题总结 websocket Websocket简介 定义和工作原理 Websocket是一种在单个TCP连接上进行全双工通信的协议。与传统的HTTP请求 - 响应模式不同,它允许服务器主动向客户端推送数据。例…...

如何做含有identify抓信号的fpga版本(image或者Bit)
在数字的FPGA debug中除了ila就是identify了,identify是synopsys公司的RTL级的调试工具。要用起来idetify,第一步就是要做出含有identify的信号的FPGA版本,quartus的是image,Ximlinx的是Bit或者Bin文件。具体有以下几步࿱…...

AIGC实践-使用Amazon Bedrock的SDXL模型进行文生图
一、Bedrock 简介 Amazon Bedrock 是 Amazon Web Services (AWS) 提供的一种生成式 AI 服务。通过 Bedrock,用户可以方便地使用多种基础模型(Foundation Models),包括 OpenAI 的 GPT、Anthropic 的 Claude 等。这些模型可以用于各…...

【源码】Sharding-JDBC源码分析之SQL中分片键路由ShardingSQLRouter的原理
Sharding-JDBC系列 1、Sharding-JDBC分库分表的基本使用 2、Sharding-JDBC分库分表之SpringBoot分片策略 3、Sharding-JDBC分库分表之SpringBoot主从配置 4、SpringBoot集成Sharding-JDBC-5.3.0分库分表 5、SpringBoot集成Sharding-JDBC-5.3.0实现按月动态建表分表 6、【…...

初学 flutter 环境变量配置
一、jdk(jdk11) 1)配置环境变量 新增:JAVA_HOMEC:\Program Files\Java\jdk-11 //你的jdk目录 在path新增:%JAVA_HOME%\bin2)验证是否配置成功(cmd运行命令) java java -version …...

【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...

[论文阅读]TrustRAG: Enhancing Robustness and Trustworthiness in RAG
TrustRAG: Enhancing Robustness and Trustworthiness in RAG [2501.00879] TrustRAG: Enhancing Robustness and Trustworthiness in Retrieval-Augmented Generation 代码:HuichiZhou/TrustRAG: Code for "TrustRAG: Enhancing Robustness and Trustworthin…...

【Veristand】Veristand环境安装教程-Linux RT / Windows
首先声明,此教程是针对Simulink编译模型并导入Veristand中编写的,同时需要注意的是老用户编译可能用的是Veristand Model Framework,那个是历史版本,且NI不会再维护,新版本编译支持为VeriStand Model Generation Suppo…...

【汇编逆向系列】六、函数调用包含多个参数之多个整型-参数压栈顺序,rcx,rdx,r8,r9寄存器
从本章节开始,进入到函数有多个参数的情况,前面几个章节中介绍了整型和浮点型使用了不同的寄存器在进行函数传参,ECX是整型的第一个参数的寄存器,那么多个参数的情况下函数如何传参,下面展开介绍参数为整型时候的几种情…...

深入理解 C++ 左值右值、std::move 与函数重载中的参数传递
在 C 编程中,左值和右值的概念以及std::move的使用,常常让开发者感到困惑。特别是在函数重载场景下,如何合理利用这些特性来优化代码性能、确保语义正确,更是一个值得深入探讨的话题。 在开始之前,先提出几个问题&…...

篇章一 论坛系统——前置知识
目录 1.软件开发 1.1 软件的生命周期 1.2 面向对象 1.3 CS、BS架构 1.CS架构编辑 2.BS架构 1.4 软件需求 1.需求分类 2.需求获取 1.5 需求分析 1. 工作内容 1.6 面向对象分析 1.OOA的任务 2.统一建模语言UML 3. 用例模型 3.1 用例图的元素 3.2 建立用例模型 …...
django paramiko 跳转登录
在使用Django框架结合Paramiko进行SSH远程操作时,通常涉及到自动化脚本的执行,比如远程服务器上的命令执行、文件传输等。如果你的需求是“跳转登录”,即在登录远程服务器后,再通过该服务器的SSH连接跳转到另一台服务器࿰…...

Appium+python自动化(八)- 认识Appium- 下章
1、界面认识 在之前安装appium的时候说过我们有两种方法安装,也就有两种结果,一种是有界面的(客户端安装),一种是没有界面的(终端安装),首先我们先讲一下有界面的,以及界…...