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>
相关推荐
程序员清风14 小时前
程序员兼职必看:靠谱软件外包平台挑选指南与避坑清单!
java·后端·面试
皮皮林55115 小时前
利用闲置 Mac 从零部署 OpenClaw 教程 !
java
华仔啊20 小时前
挖到了 1 个 Java 小特性:var,用完就回不去了
java·后端
SimonKing21 小时前
SpringBoot整合秘笈:让Mybatis用上Calcite,实现统一SQL查询
java·后端·程序员
日月云棠2 天前
各版本JDK对比:JDK 25 特性详解
java
用户8307196840822 天前
Spring Boot 项目中日期处理的最佳实践
java·spring boot
JavaGuide2 天前
Claude Opus 4.6 真的用不起了!我换成了国产 M2.5,实测真香!!
java·spring·ai·claude code
IT探险家2 天前
Java 基本数据类型:8 种原始类型 + 数组 + 6 个新手必踩的坑
java
花花无缺2 天前
搞懂new 关键字(构造函数)和 .builder() 模式(建造者模式)创建对象
java
用户908324602732 天前
Spring Boot + MyBatis-Plus 多租户实战:从数据隔离到权限控制的完整方案
java·后端