在 MyBatis 中,插件(Interceptor)是一种面向切面编程(AOP)的机制,允许在 SQL 执行的生命周期(如 查询、更新、插入、删除等操作)中插入自定义逻辑,从而对 MyBatis 的行为进行增强或修改。

? 1. MyBatis 插件的原理

MyBatis 插件的底层实现基于 动态代理(Java Proxy),通过实现 org.apache.ibatis.plugin.Interceptor 接口,使用 责任链模式 拦截 MyBatis 的核心方法,从而在方法执行前后插入自定义逻辑。

MyBatis 的拦截器可以拦截以下四种类型的方法(即 MyBatis 的四大核心对象):

核心对象说明
Executor执行 SQL 语句的对象,负责增、删、改、查的执行
ParameterHandler处理 SQL 语句中的参数
ResultSetHandler处理查询结果集
StatementHandler处理 SQL 语句的创建和参数设置

?️ 2. 插件的工作机制

  1. MyBatis 在运行时会为核心对象创建一个 代理对象(Proxy)。
  2. 代理对象会调用插件中的 intercept() 方法。
  3. 如果插件链中存在多个插件,MyBatis 会通过 责任链模式 依次调用这些插件。
  4. 在插件的 intercept() 方法中,可以:
    • 修改传入参数
    • 修改返回结果
    • 记录 SQL 执行时间
    • 打印日志
    • 添加自定义逻辑

?️ 3. 自定义插件的实现

MyBatis 插件的实现主要包括以下 4 个步骤:

① 实现 Interceptor 接口

创建一个类实现 org.apache.ibatis.plugin.Interceptor 接口,并重写其中的方法:

  • intercept(Invocation invocation): 插件的核心逻辑
  • plugin(Object target): 生成代理对象
  • setProperties(Properties properties): 接收插件配置的参数

示例:自定义一个打印 SQL 语句和执行时间的插件

1️⃣ 创建插件类

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}
    )
})
public class MyLoggingPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long startTime = System.currentTimeMillis();

        // 执行目标方法
        Object result = invocation.proceed();

        long endTime = System.currentTimeMillis();
        System.out.println("SQL 执行时间:" + (endTime - startTime) + " 毫秒");
        
        return result;
    }

    @Override
    public Object plugin(Object target) {
        // 使用 MyBatis 提供的 Plugin.wrap() 生成代理对象
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 接收配置参数
        String logLevel = properties.getProperty("logLevel");
        System.out.println("配置的日志级别:" + logLevel);
    }
}

2️⃣ 在 MyBatis 配置文件中配置插件

在 mybatis-config.xml 文件中配置插件:

<plugins>
    <plugin interceptor="com.example.plugins.MyLoggingPlugin">
        <property name="logLevel" value="INFO"/>
    </plugin>
</plugins>

3️⃣ 启用插件

在创建 MyBatis SqlSessionFactory 时加载配置:

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

? 4. 插件的核心方法解释

方法说明
intercept(Invocation invocation)插件的核心逻辑,MyBatis 在拦截到对应的方法后会调用此方法。可以修改入参或返回值。
plugin(Object target)包装目标对象,生成代理对象。通过 Plugin.wrap() 方法返回代理对象。
setProperties(Properties properties)设置插件参数。通过配置文件中的 <property> 标签注入参数。

? 5. 插件的常见使用场景

✅ SQL 日志记录
✅ SQL 执行耗时监控
✅ SQL 参数加密/解密
✅ 结果集转换或敏感数据脱敏
✅ 动态 SQL 拼接或修改
✅ 分页插件(如 PageHelper)

? 6. 拦截对象及其拦截方法

对象常用拦截方法说明
Executorupdate, query, commit, rollback拦截增删改查操作
StatementHandlerprepare, parameterize, batch拦截 SQL 语句生成和执行
ParameterHandlergetParameterObject, setParameters拦截 SQL 参数的处理
ResultSetHandlerhandleResultSets, handleOutputParameters拦截结果集的处理

? 7. 关键源码分析

MyBatis 在创建核心对象时,调用 InterceptorChain.pluginAll(target) 方法来应用插件:

InterceptorChain.java(核心逻辑)

public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
        target = interceptor.plugin(target);
    }
    return target;
}

Plugin.java(生成代理)

public static Object wrap(Object target, Interceptor interceptor) {
    return Proxy.newProxyInstance(
        target.getClass().getClassLoader(),
        getAllInterfaces(target),
        new InvocationHandler(target, interceptor)
    );
}

Invocation.java(代理执行)

public Object proceed() throws Throwable {
    return method.invoke(target, args);
}

? 8. 示例:分页插件(PageHelper)工作机制

以常见的分页插件 PageHelper 为例,PageHelper 的原理也是基于 MyBatis 插件机制:

  • StatementHandler.prepare() → 插入 LIMIT 语句
  • ResultSetHandler.handleResultSets() → 处理分页结果集
  • Executor.query() → 处理分页对象的返回

PageHelper 示例:

PageHelper.startPage(1, 10);
List<User> users = userMapper.getUsers();
PageInfo<User> pageInfo = new PageInfo<>(users);

✅ 9. 插件开发注意事项

? 插件不会对 MyBatis 的所有方法进行拦截,必须明确声明拦截的对象和方法
? 插件需要注册到 MyBatis 配置文件中才能生效
? 如果多个插件存在,MyBatis 会按照 配置顺序 执行

? 结论

MyBatis 插件是通过动态代理和责任链模式实现的,允许在 SQL 执行的各个生命周期阶段插入自定义逻辑。

  • 实现接口 Interceptor
  • 使用 @Signature 注解声明拦截方法
  • 通过 Plugin.wrap() 生成代理对象
  • 插件机制的强大之处在于可以灵活地增强和扩展 MyBatis 的功能 ?