MyBatis插件(拦截器)

MyBatis插件(拦截器)

一、核心概念:什么是MyBatis插件?
  • MyBatis插件本质上是基于 JDK动态代理 实现的 拦截器(Interceptor)。

  • 它允许你在MyBatis执行核心流程的特定时机"插入"自定义逻辑,对原有行为进行增强或修改。

  • 重要限制: MyBatis插件只能拦截指定四大核心接口中的方法,并非任意方法。

二、运行原理:JDK动态代理与拦截点

1. 核心拦截目标(四大接口) MyBatis将插件功能锚定在SQL执行生命周期的四个关键环节,通过拦截这些接口的实现类方法来实现功能扩展:

拦截接口 职责 可实现的典型插件功能
Executor 执行器 。MyBatis核心,负责SQL语句生成、缓存管理、调用StatementHandler。 分页插件 (修改SQL)、缓存增强 、批量操作优化。
StatementHandler 语句处理器 。负责创建Statement、参数化设置、执行SQL、将ResultSet交给ResultSetHandler。 SQL执行时间统计 、SQL改写 、参数统一处理。
ParameterHandler 参数处理器 。将用户传入的Java参数转换为JDBC所需的类型并设置到PreparedStatement中。 参数加密/解密 、参数格式校验 、敏感信息脱敏。
ResultSetHandler 结果集处理器 。将JDBC返回的ResultSet转换为Java对象列表。 结果集数据脱敏 、统一数据格式化 、自定义类型转换。

2. 实现原理:动态代理链

  1. MyBatis启动时,会根据配置或注解,为上述四大接口的目标对象(如SimpleExecutor)创建代理对象。

  2. 当调用被拦截的方法时(如Executor.query()),调用会首先进入拦截器链。

  3. 拦截器链中的每个插件(拦截器)按配置顺序依次执行intercept方法。

  4. 在插件的intercept方法中,开发者可以:

    • 前置处理: 在调用 invocation.proceed() 之前执行逻辑(如修改SQL参数)。

    • 执行原方法: 通过 invocation.proceed() 将调用传递下去,最终执行原始目标方法。

    • 后置处理: 在 invocation.proceed() 之后执行逻辑(如记录执行结果、修改返回数据)。

  5. 所有插件执行完毕后,最终结果返回给调用者。

三、如何编写一个MyBatis插件(三步法)

以下以实现一个"可执行SQL打印插件"为例,展示完整开发流程。

第1步:实现 Interceptor 接口,编写核心逻辑

复制代码
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.sql.Connection;
import java.util.Properties;
​
@Intercepts({
        @Signature(type = StatementHandler.class, // 指定要拦截的接口
                method = "prepare", // 指定要拦截的方法名
                args = {Connection.class, Integer.class}) // 指定方法参数类型,用于精确匹配重载方法
})
@Component // 交由Spring管理,也可在mybatis-config.xml中配置
public class ExecutableSqlLoggerPlugin implements Interceptor {
​
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 1. 前置处理:获取原始的StatementHandler (被代理的目标对象)
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        
        // 通过MetaObject等工具,从statementHandler中解析出BoundSql对象
        // BoundSql包含了最终的SQL语句和参数映射
        BoundSql boundSql = statementHandler.getBoundSql();
        String originalSql = boundSql.getSql();
        Object parameterObject = boundSql.getParameterObject();
        
        // 2. (核心)将参数与SQL合并,生成可直接在数据库客户端执行的SQL字符串
        String executableSql = generateExecutableSql(originalSql, parameterObject);
        System.out.println("【可执行SQL】: " + executableSql);
        
        // 3. 继续执行原方法(即StatementHandler.prepare)
        Object result = invocation.proceed();
        
        // 4. 后置处理(本例无需后置处理)
        return result;
    }
​
    @Override
    public Object plugin(Object target) {
        // MyBatis提供的标准方法,用于包装目标对象,创建代理
        return Plugin.wrap(target, this);
    }
​
    @Override
    public void setProperties(Properties properties) {
        // 接收来自配置文件(mybatis-config.xml)中传入的属性,可用于插件配置化
    }
    
    // 辅助方法:将参数替换到SQL中的占位符(‘?’),生成完整SQL(需处理字符串转义等)
    private String generateExecutableSql(String sql, Object parameterObject) {
        // 这里需要根据parameterObject的类型(Map、Bean、单个参数等)进行复杂解析
        // 简化示例:假设参数已正确处理,此处仅示意
        // 实际开发中,可参考开源工具如MyBatis Log Plugin的实现
        return sql.replaceFirst("\\?", "'" + parameterObject.toString() + "'");
    }
}

第2步:使用 @Intercepts 和 @Signature 注解声明拦截点

  • @Intercepts: 标识该类是一个MyBatis拦截器。

  • @Signature : 定义具体的拦截点。一个拦截器可以用多个@Signature拦截不同接口或方法。

    • type: 指定要拦截的四大接口之一。

    • method: 指定要拦截的方法名。

    • args: 指定方法的参数类型列表(Class<?>[]),用于精确匹配重载方法。

第3步:注册插件

  • Spring Boot/Spring 集成项目 : 如上例所示,使用 @Component 注解将插件类声明为Spring Bean,MyBatis-Spring-Boot-Starter会自动发现并注册它。

  • 纯MyBatis项目 : 在 mybatis-config.xml 配置文件中注册:

    复制代码
    <plugins>
        <plugin interceptor="com.yourpackage.ExecutableSqlLoggerPlugin">
            <!-- 可以通过property传递参数给插件 -->
            <!-- <property name="someProperty" value="100"/> -->
        </plugin>
    </plugins>
相关推荐
学长毕设5 小时前
基于SpringBoot的实验室资产管理系统
java·spring boot·后端
zzu123zsw6 小时前
从臃肿的模板调用到声明式契约:深入 UniHttp 动态代理与多渠道生命周期扩展
java·spring boot·microsoft·unihttp
源码集结号7 小时前
云诊所门诊系统源码,基于SpringBoot+Vue.js架构,支持多租户设计与Docker容器化部署
java·spring boot·源码·医院信息系统·门诊系统·诊所系统
论文复现现场7 小时前
WRF、FEA 能用 L40S 加速吗?求解器检查、断点恢复与长期运行
java·数据库·算法·wrf·有限元分析·gpu计算·断点续算
啃瓜子的松鼠7 小时前
泛微OAe9-后端二开异步调用
java
秋天的怀念_David8 小时前
第二十一夜 · 数字黑洞与西西弗斯的惩罚
java·程序人生·算法
外收内放8 小时前
Python基础语法练习题(73)
java·前端·python
Wang's Blog8 小时前
Java 项目实战: 交友项目 - 环信后台发送消息测试
java·交友
用户3721574261359 小时前
Java 操作 Word 脚注和尾注:添加、读取与删除
java
小嘉丶学长9 小时前
Day14 · 字典与集合
java·开发语言·网络·python·学习·1024程序员节