mybatis 如何编写一个自定义插件?

MyBatis自定义插件通过实现Interceptor接口,结合@Intercepts和@Signature注解拦截Executor、ParameterHandler、ResultSetHandler、StatementHandler四大接口,在不修改源码的前提下,于SQL执行关键节点插入逻辑,实现功能扩展、性能监控等;需注意调用invocation.proceed()、避免性能开销、处理多插件顺序及线程安全,并确保外部操作与事务一致性。

mybatis 如何编写一个自定义插件?

MyBatis自定义插件的核心,在于它提供了一种非侵入式的能力,让你能在MyBatis执行SQL的生命周期中,插入自己的逻辑。简单来说,它就像在MyBatis内部流程的几个关键节点上安插了“哨兵”,这些“哨兵”可以在数据准备、SQL执行、结果处理等环节,读取、修改甚至替换掉原本的行为,而这一切都不需要你动MyBatis源码一根指头。这对于功能扩展、性能监控、日志记录或者数据权限控制来说,简直是神来之笔。

解决方案

要编写一个MyBatis自定义插件,你主要需要实现MyBatis提供的

Interceptor

接口。这个接口有三个核心方法:

intercept

plugin

setProperties

实现

Interceptor

接口:这是插件的骨架。使用

@Intercepts

@Signature

注解:这些注解是告诉MyBatis你的插件想要拦截哪个接口的哪个方法。一个插件可以拦截多个方法,甚至多个接口。

@Intercepts

: 标记这是一个拦截器。

@Signature

: 定义要拦截的方法签名。

type

指定要拦截的接口(

Executor

,

ParameterHandler

,

ResultSetHandler

,

StatementHandler

之一),

method

指定该接口下的方法名,

args

指定该方法的参数类型列表。实现

intercept(Invocation invocation)

方法:这是插件的核心逻辑所在。

invocation

对象包含了被拦截的目标对象(

getTarget()

)、被拦截的方法(

getMethod()

)以及方法参数(

getArgs()

)。你可以在这里执行你的业务逻辑,然后必须调用

invocation.proceed()

来放行,让MyBatis继续执行原来的流程,否则整个链条就断了。实现

plugin(Object target)

方法:这个方法的作用是,当MyBatis发现你的插件需要拦截某个目标对象时,会调用这个方法来生成一个代理对象。你通常会在这里返回

Plugin.wrap(target, this)

,让MyBatis帮你生成一个代理,以便拦截器能够真正地拦截到目标对象的方法调用。实现

setProperties(Properties properties)

方法:这个方法用于在插件初始化时,接收你在MyBatis配置文件中为插件设置的属性。如果你需要一些配置参数,可以通过这里获取。

mybatis-config.xml

中注册插件:最后一步,你需要告诉MyBatis你的插件在哪里。

这是一个简单的示例,展示如何拦截

Executor

update

方法,并打印SQL执行时间:

import org.apache.ibatis.executor.Executor;import org.apache.ibatis.mapping.MappedStatement;import org.apache.ibatis.plugin.*;import org.apache.ibatis.session.ResultHandler;import org.apache.ibatis.session.RowBounds;import java.util.Properties;@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 PerformanceInterceptor implements Interceptor {    private String authorName; // 演示如何获取配置属性    @Override    public Object intercept(Invocation invocation) throws Throwable {        long start = System.currentTimeMillis();        try {            // 这是最关键的一步,放行,让MyBatis继续执行原有的方法            Object result = invocation.proceed();            return result;        } finally {            long end = System.currentTimeMillis();            System.out.println("方法:" + invocation.getMethod().getName() + " 执行耗时:" + (end - start) + "ms. 作者: " + authorName);            // 还可以获取更多信息,比如 invocation.getTarget() 得到被代理的Executor对象            // 或者 invocation.getArgs() 得到方法的参数        }    }    @Override    public Object plugin(Object target) {        // 返回一个代理对象,如果目标对象是Executor,就为其创建代理        // 这样MyBatis在调用Executor的方法时,就会先经过这个拦截器        return Plugin.wrap(target, this);    }    @Override    public void setProperties(Properties properties) {        // 在这里可以获取到MyBatis配置文件中为该插件设置的属性        this.authorName = properties.getProperty("author");        if (this.authorName == null) {            this.authorName = "Unknown";        }        System.out.println("PerformanceInterceptor 初始化,获取到属性 author=" + authorName);    }}

然后在

mybatis-config.xml

中注册:

                                            

MyBatis插件能拦截哪些核心操作?

MyBatis的插件机制,本质上是基于JDK动态代理实现的。它允许你拦截MyBatis内部的四个核心接口。理解这些接口各自的职责,是写好插件的前提。

Executor

(执行器):这是MyBatis执行SQL语句的核心。所有的SQL操作,无论是查询还是更新,最终都会通过

Executor

来完成。拦截

Executor

,你可以在SQL执行前进行一些预处理,比如权限校验、数据源切换,或者在执行后进行性能统计。这是最常用的拦截点,因为它处于SQL执行的“大门口”。例如,你可以在这里获取到

MappedStatement

对象,从而知道当前执行的是哪个SQL。

ParameterHandler

(参数处理器):这个接口负责处理SQL语句的参数。它会把Java对象中的参数,正确地设置到JDBC的

PreparedStatement

中。如果你需要对传入的参数进行加密、脱敏,或者进行一些复杂的参数转换,

ParameterHandler

就是你的目标。比如,你想把一个Java枚举类型自动转换为数据库中的特定字符串或数字,就可以在这里动手脚。

ResultSetHandler

(结果集处理器):顾名思义,它负责处理JDBC返回的结果集(

ResultSet

),并将其映射成Java对象。如果你需要对从数据库中读取出来的数据进行二次处理,比如解密、格式化,或者进行一些聚合操作,那么就应该拦截

ResultSetHandler

。这对于统一的数据清洗或视图层数据准备很有用。

StatementHandler

(语句处理器):这个接口负责构建JDBC的

Statement

对象,包括SQL语句的准备、参数的设置以及结果集的处理。它更接近于JDBC层的操作。拦截

StatementHandler

,你可以在SQL语句真正发送到数据库之前,对其进行修改(比如动态添加

WHERE

条件)、分页SQL的重写,或者获取到最终要执行的SQL字符串。这对于实现复杂的分页逻辑或者多租户数据隔离尤其有用。

通常情况下,我们最常打交道的是

Executor

StatementHandler

,因为它们能让你在SQL执行的关键节点进行干预。而

ParameterHandler

ResultSetHandler

则更专注于数据的“进出”处理。

编写MyBatis插件时常见的“坑”有哪些?

写MyBatis插件,虽然功能强大,但确实有些地方稍不注意就会掉坑里。我个人就遇到过几次,挺让人抓狂的。

一览AI编剧 一览AI编剧

创意生成+情节生成+脚本生成,AI编剧3步走,AI自动帮你搞定剧情!

一览AI编剧 87 查看详情 一览AI编剧 忘记调用

invocation.proceed()

:这是最常见的错误,没有之一。如果你在

intercept

方法里执行完自己的逻辑后,忘记调用

invocation.proceed()

,那么MyBatis的原始流程就会被中断,SQL根本不会被执行,或者结果集无法被正确处理。这就像你在一个流水线上,把东西拿过来检查了,但没放回传送带,后面的人就没法继续工作了。

ClassCastException

:MyBatis的插件机制是通过动态代理实现的。

invocation.getTarget()

返回的是一个代理对象,它可能并不是你预期的原始对象类型。在某些情况下,你可能需要进行类型判断或者通过更通用的接口来操作。尤其是在处理

args

参数时,务必小心参数的类型和顺序,否则很容易在运行时抛出类型转换异常。性能开销:插件会在每次被拦截的方法调用时执行。如果你在

intercept

方法里做了大量耗时操作,比如复杂的计算、网络请求或者文件I/O,那对整个应用的性能影响是巨大的。插件应该尽可能地轻量级,或者只在必要时才执行重型操作。我曾经见过有人在拦截器里做RPC调用,结果整个系统慢得像蜗牛。多插件的执行顺序:当配置了多个插件时,它们的执行顺序是按照在

mybatis-config.xml

中配置的顺序来决定的。前一个插件的

plugin

方法会返回一个代理对象,这个代理对象又会作为下一个插件的

target

。如果插件之间有依赖关系,或者对同一个目标对象进行修改,顺序就变得非常重要。调试这种问题会比较头疼,因为你得理清代理链。

MappedStatement

的误解:在拦截

Executor

时,

MappedStatement

是一个非常重要的参数,它包含了SQL ID、SQL类型、参数映射等大量信息。但

MappedStatement

是MyBatis在启动时构建的,它是不可变的。如果你想修改SQL,不能直接修改

MappedStatement

,而是需要通过创建新的

MappedStatement

来替换,或者在

StatementHandler

中直接修改SQL字符串。直接修改

MappedStatement

的属性是无效的,因为你操作的是一个不可变对象的引用。线程安全问题:如果你的插件内部持有状态(比如一个计数器、一个缓存),并且这个状态在多个线程之间共享,那么你必须确保它是线程安全的。否则,在高并发环境下,数据可能会出现混乱。大部分情况下,插件的

intercept

方法应该是无状态的,或者只处理当前请求的局部状态。

自定义插件如何与MyBatis的事务管理协同工作?

自定义MyBatis插件与事务管理协同工作,其实是一个比较自然的过程,因为插件本身就是MyBatis执行流程的一部分,它运行在MyBatis所管理的事务上下文之内。

事务边界内的操作:当你通过插件拦截

Executor

并执行SQL操作时,例如在

update

query

方法拦截器中进行数据修改,这些修改是默认包含在当前MyBatis事务中的。这意味着,如果外部调用者开启了一个事务,你的插件内部进行的任何数据库操作,都将受这个事务的控制。如果事务最终回滚,你的插件所做的修改也会随之回滚。这通常是我们期望的行为,保持了数据的一致性。

外部操作与事务:如果你的插件在

intercept

方法中,除了调用

invocation.proceed()

之外,还执行了额外的、与当前MyBatis事务无关的操作,比如:

向另一个数据库写入日志。调用一个外部API。写入文件系统。这些操作不会被当前MyBatis事务管理。如果MyBatis事务回滚,这些外部操作不会自动回滚。在这种情况下,你需要自己考虑这些操作的原子性和一致性。你可能需要:在插件内部开启一个独立的事务来管理这些外部操作。使用消息队列等异步机制来解耦,或者通过补偿事务来处理回滚情况。确保这些外部操作本身是幂等的,即使重复执行也不会导致问题。

Executor

拦截与事务控制:在

Executor

层面的拦截器,对于理解事务行为尤其重要。MyBatis的事务管理主要由

SqlSession

和其内部的

Executor

来协调。当你拦截

Executor

时,你实际上是在事务的“核心”部分进行操作。

你可以获取到当前

SqlSession

,甚至可以获取到其内部的

Transaction

对象(虽然直接操作

Transaction

不常见,但可以获取其状态)。如果你在

intercept

方法中抛出异常,并且这个异常没有被捕获,MyBatis的事务管理器通常会捕获这个异常并触发事务回滚。这是MyBatis事务的默认行为,插件的行为会融入其中。

总的来说,插件在MyBatis事务的“保护伞”下运行。大部分情况下,你不需要为插件的数据库操作单独考虑事务,它们会自然地融入当前会话的事务中。只有当你执行与MyBatis当前数据库连接无关的外部操作时,才需要特别注意事务边界和一致性问题。

以上就是mybatis 如何编写一个自定义插件?的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/595495.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
Win10断网状态下如何安装网卡驱动(解决Win10断网问题的步骤和技巧)
上一篇 2025年11月10日 18:02:35
使用Robot Framework与Selenium处理不确定弹窗的稳健策略
下一篇 2025年11月10日 18:02:42

相关推荐

  • 修复Django电商项目中AJAX过滤产品列表图片不显示问题

    在Django电商项目中,当使用AJAX动态加载过滤后的产品列表时,常遇到图片无法正常显示的问题。这通常是由于前端模板中图片加载方式(如data-setbg属性结合JavaScript库)与AJAX动态内容更新机制不兼容所致。解决方案是直接在AJAX返回的HTML中使用标准的标签来渲染图片,确保浏览…

    2026年5月10日
    000
  • Matplotlib 地图中多类型图例的创建与优化

    Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化

    本教程旨在解决matplotlib地图可视化中,如何在一个图例中同时展示颜色块(如区域分类)和自定义标记(如特定兴趣点)的问题。文章详细介绍了当传统`patch`对象无法正确显示标记时,如何利用`matplotlib.lines.line2d`创建标记图例句柄,并将其与颜色块图例句柄合并,从而生成一…

    2026年5月10日 用户投稿
    100
  • 怎么在PHP代码中实现图片上传功能_PHP图片上传功能实现与安全处理教程

    首先创建含enctype的HTML表单,再用PHP接收文件,检查目录、移动临时文件,验证类型与大小,生成唯一文件名,并调整php.ini限制以确保上传成功。 如果您尝试在PHP项目中添加图片上传功能,但服务器无法正确接收或保存文件,则可能是由于表单配置、文件处理逻辑或安全限制的问题。以下是实现该功能…

    2026年5月10日
    100
  • 如何让动态追加元素的类事件生效?

    如何在追加元素后使其绑定类事件生效 在页面中引入三方 JavaScript 类并通过添加相应 class 来调用事件方法是一种常见的做法。然而,如果通过 JavaScript 追加标签元素,即使添加了对应的 class,事件也可能无法生效。 为了解决这个问题,可以尝试以下步骤: 检查追加的标签是否为…

    2026年5月10日
    000
  • RichHandler与Rich Progress集成:解决显示冲突的教程

    在使用rich库的`richhandler`进行日志输出并同时使用`progress`组件时,可能会遇到显示错乱或溢出问题。这通常是由于为`richhandler`和`progress`分别创建了独立的`console`实例导致的。解决方案是确保日志处理器和进度条组件共享同一个`console`实例…

    2026年5月10日
    000
  • 修复点击时按钮抖动:CSS垂直对齐实践

    本文探讨了在Web开发中,交互式按钮(如播放/暂停按钮)在点击时发生意外垂直位移的问题。通过分析CSS样式变化对元素布局的影响,我们发现这是由于按钮不同状态下的边框样式和内边距改变,以及默认的垂直对齐行为共同作用所致。核心解决方案是利用CSS的vertical-align属性,将其设置为middle…

    2026年5月10日
    100
  • 使用 Jupyter Notebook 进行探索性数据分析

    Jupyter Notebook通过单元格实现代码与Markdown结合,支持数据导入(pandas)、清洗(fillna)、探索(matplotlib/seaborn可视化)、统计分析(describe/corr)和特征工程,便于记录与分享分析过程。 Jupyter Notebook 是进行探索性…

    2026年5月10日
    000
  • 如何在HTML中插入表单元素_HTML表单控件与输入类型使用指南

    HTML表单通过标签构建,包含action和method属性定义数据提交目标与方式,常用input类型如text、password、email等适配不同输入需求,配合label、required、placeholder提升可用性,结合textarea、select、button等控件实现完整交互,是…

    2026年5月10日
    100
  • 前端缓存策略与JavaScript存储管理

    根据数据特性选择合适的存储方式并制定清晰的读写与清理逻辑,能显著提升前端性能;合理运用Cookie、localStorage、sessionStorage、IndexedDB及Cache API,结合缓存策略与定期清理机制,可在保证用户体验的同时避免安全与性能隐患。 前端缓存和JavaScript存…

    2026年5月10日
    200
  • HTML5网页如何实现手势操作 HTML5网页移动端交互的处理技巧

    首先利用原生touch事件实现滑动判断,再通过preventDefault解决滚动冲突,接着引入Hammer.js处理复杂手势,最后通过优化点击区域、避免事件冲突和增加视觉反馈提升体验。 在移动端浏览器中,HTML5网页可以通过触摸事件实现手势操作,提升用户体验。虽然原生JavaScript提供了基…

    2026年5月10日
    000
  • 使用 WebCodecs VideoDecoder 实现精确逐帧回退

    本文档旨在解决在使用 WebCodecs VideoDecoder 进行视频解码时,实现精确逐帧回退的问题。通过比较帧的时间戳与目标帧的时间戳,可以避免渲染中间帧,从而提高用户体验。本文将提供详细的解决方案和示例代码,帮助开发者实现精确的视频帧控制。 在使用 WebCodecs VideoDecod…

    2026年5月10日
    000
  • JavaScript 闭包:理解闭包原理与内存泄漏问题

    闭包是函数访问其外部作用域变量的能力,即使外部函数已执行完毕。如 inner 函数引用 outer 中的 count,形成闭包,使变量持久存在。闭包本身无害,但可能因延长变量生命周期导致内存泄漏,例如事件监听器引用大对象时。若未及时清理 DOM 事件或定时器,闭包会阻止垃圾回收,造成内存占用过高。解…

    2026年5月10日
    100
  • JavaScript 动态菜单点击高亮效果实现教程

    本教程详细介绍了如何使用 JavaScript 实现动态菜单的点击高亮功能。通过事件委托和状态管理,当用户点击菜单项时,被点击项会高亮显示(绿色),同时其他菜单项恢复默认样式(白色)。这种方法避免了不必要的DOM操作,提高了性能和代码可维护性,确保了无论点击方向如何,功能都能稳定运行。 动态菜单高亮…

    2026年5月10日
    200
  • html5怎么画实线_HTML5用CSS border-style:solid画元素实线边框【绘制】

    可通过CSS的border-style属性设为solid添加实线边框:一、内联样式用border:2px solid #000;二、内部样式表统一设置如div{border:1px solid #333};三、外部CSS文件定义.my-box{border:3px solid red}并引入;四、单…

    2026年5月10日
    400
  • JavaScript函数中插入加载动画(Spinner)的正确方法

    本文旨在解决在JavaScript函数中插入加载动画(Spinner)时遇到的异步问题。通过引入async/await和Promise.all,确保在数据处理完成前后正确显示和隐藏加载动画,提升用户体验。我们将提供两种实现方案,并详细解释其原理和优势。 在Web开发中,当执行耗时操作时,显示加载动画…

    2026年5月10日
    100
  • JS如何实现迭代器?迭代器协议

    JavaScript中实现迭代器需遵循可迭代协议和迭代器协议,通过定义[Symbol.iterator]方法返回具备next()方法的迭代器对象,从而支持for…of和展开运算符;该机制统一了数据结构的遍历接口,实现惰性求值,适用于自定义对象、树、图及无限序列等复杂场景,提升代码通用性与…

    2026年5月10日
    100
  • 使用 Pydantic v2 实现条件性必填字段

    本文介绍了如何在 Pydantic v2 模型中实现条件性必填字段。通过自定义验证器,可以根据模型中其他字段的值来动态地控制某些字段是否为必填项,从而满足 API 交互中数据验证的复杂需求。本文提供了一个具体的示例,展示了如何确保模型中至少有一个字段被赋值。 在 Pydantic v2 中,虽然没有…

    2026年5月10日
    000
  • 三星不再独享,消息称搭载骁龙 8 Gen 3 领先版处理器新机即将发布

    三星不再独享,消息称搭载骁龙 8 Gen 3 领先版处理器新机即将发布三星不再独享,消息称搭载骁龙 8 Gen 3 领先版处理器新机即将发布三星不再独享,消息称搭载骁龙 8 Gen 3 领先版处理器新机即将发布三星不再独享,消息称搭载骁龙 8 Gen 3 领先版处理器新机即将发布

    6 月 15 日消息,据博主@肥威 今日爆料,搭载骁龙 8 Gen 3 领先版%ign%ignore_a_1%re_a_1%的新机即将发布,把之前的 for Galaxy 改成“for Everybody”。 Pic Copilot AI时代的顶级电商设计师,轻松打造爆款产品图片 158 查看详情 …

    2026年5月10日 用户投稿
    100
  • 动态更新圆形进度条:JavaScript成绩计算器集成指南

    本文档旨在指导开发者如何将JavaScript成绩计算系统与动态圆形进度条集成,实现可视化展示平均成绩。我们将详细讲解如何修改现有的JavaScript代码,使其在计算出平均分后,能够动态更新圆形进度条的进度,从而提供更直观的用户体验。本文档包含详细的代码示例和注意事项,帮助开发者轻松实现这一功能。…

    2026年5月10日
    000
  • React组件中动态属性值的管理与同步:利用状态实现受控组件

    本教程旨在解决react组件中动态属性值同步使用的问题。我们将探讨如何利用react的`usestate` hook来管理组件内部状态,从而实现一个属性的值动态地影响另一个属性,并构建出可预测、易于维护的受控组件。文章将通过具体代码示例,详细阐述从初始化状态到处理状态更新的完整过程,并强调受控组件在…

    2026年5月10日
    000

发表回复

登录后才能评论
关注微信