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)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月10日 17:57:07
下一篇 2025年11月10日 18:09:27

相关推荐

  • SASS 中的 Mixins

    mixin 是 css 预处理器提供的工具,虽然它们不是可以被理解的函数,但它们的主要用途是重用代码。 不止一次,我们需要创建多个类来执行相同的操作,但更改单个值,例如字体大小的多个类。 .fs-10 { font-size: 10px;}.fs-20 { font-size: 20px;}.fs-…

    2025年12月24日
    000
  • React 或 Vite 是否会自动加载 CSS?

    React 或 Vite 是否自动加载 CSS? 在 React 中,如果未显式导入 CSS,而页面却出现了 CSS 效果,这可能是以下原因造成的: 你使用的第三方组件库,例如 AntD,包含了自己的 CSS 样式。这些组件库在使用时会自动加载其 CSS 样式,无需显式导入。在你的代码示例中,cla…

    2025年12月24日
    000
  • React 和 Vite 如何处理 CSS 加载?

    React 或 Vite 是否会自动加载 CSS? 在 React 中,默认情况下,使用 CSS 模块化时,不会自动加载 CSS 文件。需要手动导入或使用 CSS-in-JS 等技术才能应用样式。然而,如果使用了第三方组件库,例如 Ant Design,其中包含 CSS 样式,则这些样式可能会自动加…

    2025年12月24日
    000
  • ElementUI el-table 子节点选中后为什么没有打勾?

    elementui el-table子节点选中后没有打勾? 当您在elementui的el-table中选择子节点时,但没有出现打勾效果,可能是以下原因造成的: 在 element-ui 版本 2.15.7 中存在这个问题,升级到最新版本 2.15.13 即可解决。 除此之外,请确保您遵循了以下步骤…

    2025年12月24日
    200
  • 如何使用 Ant Design 实现自定义的 UI 设计?

    如何使用 Ant Design 呈现特定的 UI 设计? 一位开发者提出: 我希望使用 Ant Design 实现如下图所示的 UI。作为一个前端新手,我不知从何下手。我尝试使用 a-statistic,但没有任何效果。 为此,提出了一种解决方案: 可以使用一个图表库,例如 echarts.apac…

    2025年12月24日
    000
  • 您不需要 CSS 预处理器

    原生 css 在最近几个月/几年里取得了长足的进步。在这篇文章中,我将回顾人们使用 sass、less 和 stylus 等 css 预处理器的主要原因,并向您展示如何使用原生 css 完成这些相同的事情。 分隔文件 分离文件是人们使用预处理器的主要原因之一。尽管您已经能够将另一个文件导入到 css…

    2025年12月24日
    000
  • Antdv 如何实现类似 Echarts 图表的效果?

    如何使用 antdv 实现图示效果? 一位前端新手咨询如何使用 antdv 实现如图所示的图示: antdv 怎么实现如图所示?前端小白不知道怎么下手,尝试用了 a-statistic,但没有任何东西出来,也不知道为什么。 针对此问题,回答者提供了解决方案: 可以使用图表库 echarts 实现类似…

    2025年12月24日
    300
  • 如何使用 antdv 创建图表?

    使用 antdv 绘制如所示图表的解决方案 一位初学前端开发的开发者遇到了困难,试图使用 antdv 创建一个特定图表,却遇到了障碍。 问题: 如何使用 antdv 实现如图所示的图表?尝试了 a-statistic 组件,但没有任何效果。 解答: 虽然 a-statistic 组件不能用于创建此类…

    2025年12月24日
    200
  • 如何在 Ant Design Vue 中使用 ECharts 创建一个类似于给定图像的圆形图表?

    如何在 ant design vue 中实现圆形图表? 问题中想要实现类似于给定图像的圆形图表。这位新手尝试了 a-statistic 组件但没有任何效果。 为了实现这样的图表,可以使用 [apache echarts](https://echarts.apache.org/) 库或其他第三方图表库…

    好文分享 2025年12月24日
    100
  • CSS 中如何正确使用 box-shadow 设置透明度阴影?

    css 中覆盖默认 box-shadow 样式时的报错问题 在尝试修改导航栏阴影时遇到报错,分析发现是 box-shadow 样式引起的问题。 问题原因 使用 !important 仍无法覆盖默认样式的原因在于,你使用了 rgb() 而不是 rgba(),这会导致语法错误。 立即学习“前端免费学习笔…

    2025年12月24日
    300
  • 为何scss中嵌套使用/*rtl:ignore*/无法被postcss-rtl插件识别?

    postcss-rtl插件为何不支持在scss中嵌套使用/*rtl:ignore*/ 在使用postcss-rtl插件时,如果希望对某个样式不进行转换,可以使用/*rtl:ignore*/在选择器前面进行声明。然而,当样式文件为scss格式时,该声明可能会失效,而写在css文件中则有效。 原因 po…

    2025年12月24日
    000
  • Sass 中使用 rgba(var –color) 时的透明度问题如何解决?

    rgba(var –color)在 Sass 中无效的解决方法 在 Sass 中使用 rgba(var –color) 时遇到透明问题,可能是因为以下原因: 编译后的 CSS 代码 rgba($themeColor, 0.8) 在编译后会变为 rgba(var(–…

    2025年12月24日
    000
  • ## PostCSS vs. Sass/Less/Stylus:如何选择合适的 CSS 代码编译工具?

    PostCSS 与 Sass/Less/Stylus:CSS 代码编译转换中的异同 在 CSS 代码的编译转换领域,PostCSS 与 Sass/Less/Stylus 扮演着重要的角色,但它们的作用却存在细微差异。 区别 PostCSS 主要是一种 CSS 后处理器,它在 CSS 代码编译后进行处…

    2025年12月24日
    000
  • echarts地图中点击图例后颜色变化的原因和修改方法是什么?

    图例颜色变化解析:echarts地图的可视化配置 在使用echarts地图时,点击图例会触发地图颜色的改变。然而,选项中并没有明确的配置项来指定此颜色。那么,这个颜色是如何产生的,又如何对其进行修改呢? 颜色来源:可视化映射 echarts中有一个名为可视化映射(visualmap)的对象,它负责将…

    2025年12月24日
    000
  • SCSS 简介:增强您的 CSS 工作流程

    在 web 开发中,当项目变得越来越复杂时,编写 css 可能会变得重复且具有挑战性。这就是 scss (sassy css) 的用武之地,它是一个强大的 css 预处理器。scss 带来了变量、嵌套、混合等功能,使开发人员能够编写更干净、更易于维护的代码。在这篇文章中,我们将深入探讨 scss 是…

    2025年12月24日
    000
  • 在 Sass 中使用 Mixin

    如果您正在深入研究前端开发世界,那么您很可能遇到过sass(语法很棒的样式表)。 sass 是一个强大的 css 预处理器,它通过提供变量、嵌套、函数和 mixins 等功能来增强您的 css 工作流程。在这些功能中,mixins 作为游戏规则改变者脱颖而出,允许您有效地重用代码并保持样式表的一致性…

    2025年12月24日
    200
  • SCSS:创建模块化 CSS

    介绍 近年来,css 预处理器的使用在 web 开发人员中显着增加。 scss (sassy css) 就是这样一种预处理器,它允许开发人员编写模块化且可维护的 css 代码。 scss 是 css 的扩展,添加了更多特性和功能,使其成为设计网站样式的强大工具。在本文中,我们将深入探讨使用 scss…

    2025年12月24日
    000
  • SCSS – 增强您的 CSS 工作流程

    在本文中,我们将探索 scss (sassy css),这是一个 css 预处理器,它通过允许变量、嵌套规则、mixins、函数等来扩展 css 的功能。 scss 使 css 的编写和维护变得更加容易,尤其是对于大型项目。 1.什么是scss? scss 是 sass(syntropically …

    2025年12月24日
    000
  • 如何正确使用 CSS:简洁高效样式的最佳实践

    层叠样式表 (css) 是 web 开发中的一项基本技术,允许设计人员和开发人员创建具有视觉吸引力和响应灵敏的网站。然而,如果没有正确使用,css 很快就会变得笨拙且难以维护。在本文中,我们将探索有效使用 css 的最佳实践,确保您的样式表保持干净、高效和可扩展。 什么是css? css(层叠样式表…

    2025年12月24日
    000
  • css网页设计模板怎么用

    通过以下步骤使用 CSS 网页设计模板:选择模板并下载到本地计算机。了解模板结构,包括 index.html(内容)和 style.css(样式)。编辑 index.html 中的内容,替换占位符。在 style.css 中自定义样式,修改字体、颜色和布局。添加自定义功能,如 JavaScript …

    2025年12月24日
    000

发表回复

登录后才能评论
关注微信