MyBatis 插件详解

227 阅读3分钟

MyBatis 插件是一种强大的扩展机制,允许开发者在不修改框架源码的情况下,拦截和增强 MyBatis 的核心组件(如 ExecutorStatementHandler 等)。以下是 MyBatis 插件的详细解析,包含工作原理、开发步骤及实际应用场景。


一、插件的工作原理

MyBatis 插件基于 动态代理责任链模式 实现,其核心流程如下:

  1. 拦截目标对象 MyBatis 在启动时,会为目标对象(如 Executor)创建代理对象,代理对象会拦截所有方法调用。
  2. 责任链调用 若存在多个插件,会按配置顺序形成责任链,依次执行插件的拦截逻辑,最后调用原始方法。
  3. 动态增强 开发者可以在方法执行前后插入自定义逻辑(如修改 SQL、记录日志等)。

二、插件的开发步骤

1. 实现 Interceptor 接口

自定义插件需实现 Interceptor 接口,并重写以下方法:

public interface Interceptor {
  Object intercept(Invocation invocation) throws Throwable;
  Object plugin(Object target); // 创建代理对象
  void setProperties(Properties properties); // 读取配置参数
}
2. 定义拦截目标

使用 @Intercepts@Signature 注解指定要拦截的类、方法及参数类型:

@Intercepts({
    @Signature(
        type = Executor.class, // 目标类
        method = "query",      // 目标方法
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
    )
})
public class MyPlugin implements Interceptor {
    // 实现方法...
}
3. 实现拦截逻辑

intercept 方法中编写增强逻辑:

@Override
public Object intercept(Invocation invocation) throws Throwable {
    System.out.println("Before executing query...");
    Object result = invocation.proceed(); // 执行原方法
    System.out.println("After executing query.");
    return result;
}
4. 注册插件

mybatis-config.xml 中添加插件配置:

<plugins>
    <plugin interceptor="com.example.MyPlugin">
        <property name="param1" value="value1"/> <!-- 可选参数 -->
    </plugin>
</plugins>

三、插件的应用场景

1. 分页插件
  • 功能:自动为 SQL 添加分页逻辑(如 LIMITOFFSET)。
  • 拦截点StatementHandler.prepare()
  • 示例:MyBatis 官方推荐的 PageHelper 插件。
2. SQL 日志打印
  • 功能:记录完整 SQL 语句和执行时间。
  • 拦截点Executor.query()Executor.update()
@Override
public Object intercept(Invocation invocation) throws Throwable {
    long start = System.currentTimeMillis();
    Object result = invocation.proceed();
    long end = System.currentTimeMillis();
    System.out.println("SQL执行耗时:" + (end - start) + "ms");
    return result;
}
3. 敏感数据脱敏
  • 功能:对查询结果中的敏感字段(如手机号、邮箱)进行脱敏处理。
  • 拦截点ResultSetHandler.handleResultSets()
4. 多租户数据隔离
  • 功能:自动在 SQL 中插入租户 ID 过滤条件。
  • 拦截点StatementHandler.prepare()

四、插件的执行顺序与配置

  1. 执行顺序 插件按配置顺序依次执行。例如,先注册的插件会先被调用。

  2. 配置参数 通过 <property> 标签传递参数到插件:

    <plugin interceptor="com.example.MyPlugin">
        <property name="logLevel" value="DEBUG"/>
    </plugin>
    

    在插件中通过 setProperties 读取参数:

    @Override
    public void setProperties(Properties properties) {
        String logLevel = properties.getProperty("logLevel", "INFO");
    }
    

五、注意事项与最佳实践

  1. 避免过度拦截 仅拦截必要的方法,频繁拦截可能影响性能。
  2. 线程安全 若插件包含共享状态,需确保线程安全(如使用 ThreadLocal)。
  3. 兼容性 不同 MyBatis 版本的拦截点可能不同,需参考官方文档。
  4. 调试技巧 在插件中打印日志或断点调试,验证拦截逻辑是否正确。

六、完整示例:SQL 执行时间监控插件

@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 SqlTimerPlugin implements Interceptor {
​
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = invocation.proceed();
        long end = System.currentTimeMillis();
        String methodName = invocation.getMethod().getName();
        System.out.println("方法 " + methodName + " 执行耗时:" + (end - start) + "ms");
        return result;
    }
​
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this); // 创建代理对象
    }
​
    @Override
    public void setProperties(Properties properties) {
        // 可读取配置参数
    }
}

注册插件

<plugins>
    <plugin interceptor="com.example.SqlTimerPlugin"/>
</plugins>

七、总结

MyBatis 插件通过动态代理机制,为开发者提供了灵活的功能扩展能力。无论是性能监控、SQL 增强还是数据脱敏,插件都能在不侵入业务代码的前提下实现。掌握插件的开发与配置,能显著提升 MyBatis 的适应性和可维护性。