MyBatis 插件原理(Interceptor)
MyBatis 允许用插件(Interceptor)在 SQL 执行的关键节点"插入"自己的逻辑,PageHelper 分页、慢 SQL 日志、数据权限过滤都是这么实现的。想理解插件,先认识它要拦截的四大核心对象,再掌握注解声明的拦截点。
四大核心对象
MyBatis 执行一次 SQL 会依次经过四个对象(由 Configuration 创建):
| 对象 | 职责 | 典型方法 |
|---|---|---|
| Executor | 执行器:调度缓存、事务与 SQL 执行(最顶层) | update、query、commit、rollback |
| StatementHandler | 语句处理器:创建 PreparedStatement、设置参数、执行 | prepare、parameterize、update、query |
| ParameterHandler | 参数处理器:把参数绑定进 SQL | setParameters |
| ResultSetHandler | 结果处理器:ResultSet 转成 Java 对象 | handleResultSets |
插件只能拦这四个对象,别的类(如 Mapper 接口)拦不了。
Interceptor 长什么样
实现 org.apache.ibatis.plugin.Interceptor 接口,重写三个方法,再用 @Intercepts + @Signature 声明"拦谁、拦哪个方法、方法参数长什么样":
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
@Intercepts({
@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "update",
args = {MappedStatement.class, Object.class})
})
public class SqlLogPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
try {
return invocation.proceed(); // 放行,执行真正的 SQL
} finally {
System.out.println("耗时:" + (System.currentTimeMillis() - start) + " ms");
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this); // 标准写法:生成代理
}
@Override
public void setProperties(Properties properties) {
// 读取 <plugin> 下配置的 <property>
}
}
注意:@Signature 里的方法参数列表必须与真实方法签名完全一致,否则拦截不生效。Executor.query 有多个重载,注册时参数类型顺序要核对(以官方文档/源码为准)。
注册插件
<plugins>
<plugin interceptor="com.example.plugin.SqlLogPlugin">
<property name="threshold" value="1000"/>
</plugin>
</plugins>
Spring/Spring Boot 集成时通过 SqlSessionFactoryBean 的 plugins 属性注册(见第 15 章)。
工作原理
- MyBatis 创建四大对象时,会检查有没有匹配的插件。
- 有匹配则用
Plugin.wrap生成 JDK 动态代理,把对象"包一层"。 - 多个插件时层层包裹成责任链:
A 代理 → B 代理 → 真实对象。 - 调用对象方法时,只有命中了 @Signature 声明的方法才进入拦截器;intercept 里调用
invocation.proceed()放行给链上下一环。
mapper 调用 → Executor 代理 → 命中? → 你的 intercept() → proceed() → 真实 Executor
常用场景与忠告
- 应用:物理分页(PageHelper 就是 Executor.query 拦截器)、SQL 审计、慢查询统计、逻辑删除改写。
- 忠告:插件会改动 MyBatis 核心执行路径,拦截器越少越清晰;不要在拦截器里依赖内部类与未公开 API(升级版本易碎,行为以官方文档为准);要改写 SQL 需处理 BoundSql 与参数映射,属于高阶操作,务必充分测试。
小结
插件 = 拦截 Executor/StatementHandler/ParameterHandler/ResultSetHandler 四大对象的切面;@Intercepts/@Signature 精确声明拦截点,Plugin.wrap 生成代理、责任链放行。分页、日志、审计都建在这套机制上。