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 的功能 😎