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

相关推荐

  • Linux interfaces 虚拟网络类型了解01

    Linux interfaces 虚拟网络类型了解01Linux interfaces 虚拟网络类型了解01Linux interfaces 虚拟网络类型了解01Linux interfaces 虚拟网络类型了解01

    在osi模型的定义中,数据链路层和物理层,以及传输层和网络层执行的任务在概念上相似:它们都提供了数据传输的方式,即沿着特定路径将数据从源点传输到目的地的方法。然而,数据链路层和物理层负责跨物理路径的通信服务,而传输层和网络层则提供由多个数据链路组成的逻辑路径或虚拟路径的通信服务。 Bridge操作指…

    2026年9月21日 用户投稿
    000
  • PHP简易路由框架构建:从URL解析到动态控制器加载的实践指南

    本文旨在指导读者构建一个基础的PHP路由系统,实现URL路径到控制器方法的高效映射。内容涵盖URL解析、控制器动态加载、方法调用以及关键的错误处理机制,特别强调如何避免常见的“未定义变量”错误和文件包含路径问题,确保路由系统稳定且易于维护。 一、路由系统核心原理 构建一个简单的php路由系统,其核心…

    2026年9月21日
    100
  • 如何在Java中理解Java I/O与NIO机制

    传统I/O是阻塞式流模型,适用于低并发场景;NIO基于缓冲区与通道,支持非阻塞和多路复用,适合高并发网络应用,核心区别在于线程模型与资源利用率。 Java中的I/O(输入/输出)与NIO(New I/O)是处理数据读写的核心机制,理解它们的区别和使用场景对开发高性能应用至关重要。传统I/O基于流模型…

    2026年9月21日
    000
  • UC浏览器网页上的文字无法选中复制怎么办 UC浏览器解决网页文字禁止复制问题

    答案:可通过开发者工具、阅读模式、打印预览、OCR识别或自定义脚本解除UC浏览器网页复制限制。具体操作依次为:开启开发者工具并执行JavaScript代码解除限制;启用阅读模式净化页面内容;使用打印预览重新渲染页面以选中文字;对截图应用OCR技术提取文本;添加书签脚本自动移除禁用选择的代码,从而实现…

    2026年9月21日
    000
  • JavaScript中的尾调用优化(TCO)在ES6中如何工作?

    尾调用是指函数的最后一个动作调用另一个函数,ES6引入尾调用优化以重用栈帧、避免内存溢出,支持真正的尾递归,如阶乘函数通过累积参数实现。 尾调用优化(Tail Call Optimization, TCO)是ES6引入的一项语言特性,目的是在特定条件下重用函数调用栈帧,避免不必要的内存增长,从而支持…

    2026年9月21日
    100
  • Java语法基础有哪些新手必学的核心知识

    掌握Java基本数据类型与变量声明,如int、double、char和boolean,并理解强类型语言特性;2. 熟悉运算符与表达式,包括算术、比较和逻辑运算符,奠定程序逻辑基础。 Java语法基础是每个初学者必须掌握的内容,只有打好根基,才能顺利进阶面向对象编程和实际项目开发。以下是新手必学的核心…

    2026年9月21日
    200
  • 升级X86架构性能大提升!极空间Z2 Ultra图赏

    升级X86架构性能大提升!极空间Z2 Ultra图赏升级X86架构性能大提升!极空间Z2 Ultra图赏升级X86架构性能大提升!极空间Z2 Ultra图赏升级X86架构性能大提升!极空间Z2 Ultra图赏

    10月23日,极空间正式推出全新双盘位nas产品——极空间z2 ultra,官方售价为1899元,参与国家补贴后仅需1457元,性价比进一步提升。 此次发布的Z2 Ultra最大的亮点在于采用X86架构处理器,相较以往使用的ARM平台,性能实现飞跃式提升,运行速度显著加快。更重要的是,新架构对Doc…

    2026年9月21日 用户投稿
    200
  • 数据库分库分表(Sharding)策略

    在现代应用程序中,随着数据量的增长,单一数据库的性能和容量往往难以满足需求。这时,数据库分库分表(Sharding)策略就成了一个关键的解决方案。那么,如何设计和实现一个有效的分库分表策略呢?让我们深入探讨一下。 在我的职业生涯中,我曾多次参与大型项目的数据库优化,其中分库分表是常见的挑战之一。我记…

    2026年9月21日
    000
  • 如何在Java中实现个人财务管理工具

    首先设计Transaction、FinanceManager和Budget核心类,实现交易记录、统计分析与预算控制功能,通过ArrayList管理数据,使用LocalDate处理日期,结合ObjectOutputStream持久化存储,初期采用Scanner构建控制台菜单实现增删查改与报表展示,后期…

    2026年9月21日
    000
  • Guava Multimap:高效获取并打印指定键的所有关联值

    guava multimap是处理一键多值映射关系的强大工具。要获取特定键的所有关联值,应直接使用其提供的`multimap#get(k)`方法。该方法会返回一个包含所有匹配值的`collection`,即使键不存在,也会返回一个空集合而非`null`,从而简化了值检索和空值处理逻辑,是比手动迭代键…

    2026年9月21日
    000
  • 控制台命令(Console Command)开发

    控制台命令是程序员日常工作中不可或缺的工具,它提高了开发效率并帮助理解和控制程序运行。1) 通过简单的文本输入,完成复杂任务,如文件管理和系统监控。2) 控制台命令可用于快速调试、测试代码和自动化重复工作。3) 开发控制台命令时需注意安全性和兼容性问题。4) 控制台命令可实现有趣功能,如监控服务器资…

    2026年9月21日
    100
  • Java Stream 高效分组计数并获取Top N元素

    本文深入探讨了如何利用java stream api对数据进行高效的分组计数,并从中提取出现频率最高的top n元素。文章首先介绍了一种简洁的基于全排序的实现方式,该方法适用于数据集较小或top n值接近总数的情况。随后,针对大数据量和小型top n场景下的性能瓶颈,文章详细阐述了如何通过自定义`c…

    2026年9月21日
    000
  • 自定义协议与主流框架(如ThinkPHP)结合

    在thinkphp中实现自定义协议可以通过中间件机制。具体步骤包括:1. 创建中间件类customprotocolmiddleware,解析和验证请求的json格式和字段。2. 在应用配置文件中添加该中间件,使所有请求经过处理。通过这种方式,可以满足特定业务需求并提升应用的灵活性和可扩展性。 在开发…

    2026年9月21日
    000
  • JSF应用中Markdown文档动态链接处理指南

    本教程旨在解决jsf web应用程序中集成markdown文档时,如何动态处理内部链接以实现页面局部更新的问题。通过结合服务器端markdown渲染和客户端javascript事件监听,我们可以拦截markdown生成的html链接点击事件,利用ajax异步加载并渲染目标markdown文件,从而在…

    2026年9月21日
    500
  • 如何基于Swoole开发自定义框架?

    基于swoole开发自定义框架可以通过以下步骤实现:1. 创建核心app类,初始化swoole服务器并定义回调函数;2. 实现路由功能,使用router类处理请求分发;3. 添加中间件支持,使用middleware类处理请求;4. 集成异步数据库操作,使用swoole的mysql协程客户端;5. 实…

    2026年9月21日
    100
  • 在Java中如何实现线程优先级控制

    Java中线程优先级通过Thread类实现,取值范围1-10,分别对应MIN_PRIORITY、NORM_PRIORITY和MAX_PRIORITY;新线程继承父线程优先级,可通过setPriority()设置;尽管高优先级线程更可能被调度,但执行顺序不保证,因受操作系统影响;应避免依赖优先级控制关…

    2026年9月21日
    000
  • 如何在Java中使用接口实现多继承效果

    Java不支持多继承,但可通过实现多个接口模拟该效果。类可同时实现Flyable、Swimmable等接口,具备多种行为能力,并能利用默认方法复用逻辑,如Loggable提供日志功能。当多个接口含同名默认方法时,需在类中显式重写以解决冲突。接口用于定义“能做什么”,抽象类描述“是什么”,因类只能单继…

    2026年9月21日
    100
  • 万人同时在线抽奖活动架构

    万人同时在线抽奖活动的系统架构应采用微服务架构、分布式数据库、redis缓存、区块链存储结果,并使用负载均衡和异步处理技术。具体包括:1.采用微服务架构和分布式数据库(如tidb)保证系统稳定性和可扩展性;2.使用redis处理抽奖逻辑,确保高效和随机性;3.将结果存入区块链,保证透明度和可验证性;…

    2026年9月21日
    000
  • Linux文件和目录管理常见命令

    Linux文件和目录管理依赖于ls、cd、mkdir、rm、cp、mv等核心命令,用于浏览、创建、删除、复制和移动文件与目录;通过find、du、grep等命令可查找文件、定位大文件并清理磁盘空间;使用rename、mmv或脚本可实现批量重命名;为安全起见,应谨慎使用rm命令,推荐结合-i选项或使用…

    2026年9月21日
    000
  • 大数据量下的批量导入/导出优化

    在大数据环境下优化批量导入/导出的方法包括:1. 使用批处理技术分批导入/导出数据,减少系统资源压力;2. 采用数据流技术如apache kafka进行实时处理,降低内存占用;3. 利用并行处理技术分配任务到多个处理器或节点,提高处理速度;4. 通过性能监控和调优识别并解决瓶颈点,以提升整体效率。 …

    2026年9月21日
    200

发表回复

登录后才能评论
关注微信