共计 3634 个字符,预计需要花费 10 分钟才能阅读完成。
一、为什么要掌握 MyBatis 插件机制?
MyBatis 插件机制允许我们在不改动核心源码的情况下增强 SQL 执行过程,它是企业开发中解决以下问题的利器:
- ✅ SQL 日志审计
- ✅ 执行时间统计
- ✅ SQL 动态加密/解密
- ✅ 分页封装
- ✅ 多租户支持
它类似于 Spring AOP,但作用范围仅限 MyBatis 支持的几个核心接口,适合用于非业务逻辑的数据层横切增强。
二、插件原理与设计机制
2.1 插件必须实现 Interceptor 接口
你需要定义一个类实现 org.apache.ibatis.plugin.Interceptor
接口:
public interface Interceptor {
/**
* 子类拦截器必须要实现的方法,
* 在该方法对内自定义拦截逻辑
* @param invocation
* @return
* @throws Throwable
*/
Object intercept(Invocation invocation) throws Throwable;
/**
生成目标类的代理对象
* 也可以根据需求不返回代理对象,这种情况下这个拦截器将不起作用
* 无特殊情况使用默认的即可
* @param target
* @return
*/
default Object plugin(Object target) {
return Plugin.wrap(target, this); // 这里是插件的入口,MyBatis 每次创建核心组件(如Executor、StatementHandler)时,都会调用 wrapAll():
}
/**
* 设置变量
* 在注册拦截器的时候设置变量,在这里可以获取到
* @param properties
*/
default void setProperties(Properties properties) {
// NOP
}
}
Plugin.wrap
本质:JDK 动态代理
2.2 插件的四种可拦截对象
MyBatis 设计中仅允许对以下 4 类接口的方法拦截:
类型 | 方法 | 说明 |
---|---|---|
Executor |
query() , update() |
执行器,负责增删改查 |
ParameterHandler |
getParameterObject() , setParameters() |
参数映射 |
ResultSetHandler |
handleResultSets() |
结果映射 |
StatementHandler |
prepare() , parameterize() 等 |
SQL 预处理与参数绑定 |
使用 @Intercepts
注解精确声明你的拦截目标。
插件定义方式:
实现 org.apache.ibatis.plugin.Interceptor
接口,并使用 @Intercepts
注解标注要拦截的方法。
@Intercepts({
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)
})
@Component
public class SqlPerformanceInterceptor implements Interceptor {
// 插件逻辑略...
}
三、插件开发进阶实战技巧
案例拓展1:查询结果加解密插件
目标:自动解密数据库中的加密字段,如手机号字段。
@Intercepts({
@Signature(
type = ResultSetHandler.class,
method = "handleResultSets",
args = {Statement.class}
)
})
public class DecryptResultInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object result = invocation.proceed();
if (result instanceof List<?>) {
for (Object obj : (List<?>) result) {
for (Field field : obj.getClass().getDeclaredFields()) {
if (field.getName().equals("phone")) {
field.setAccessible(true);
String encrypted = (String) field.get(obj);
field.set(obj, AESUtil.decrypt(encrypted));
}
}
}
}
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {}
}
案例拓展2:SQL 性能分析插件
目标:拦截查询 SQL,记录耗时超过阈值的慢 SQL 日志。
@Intercepts({
@Signature(
type = Executor.class,
method = "query",
args = {
MappedStatement.class,
Object.class,
RowBounds.class,
ResultHandler.class
}
)
})
public class SqlPerformanceInterceptor implements Interceptor {
private long threshold = 500; // 毫秒
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
Object result = invocation.proceed();
long duration = System.currentTimeMillis() - start;
if (duration > threshold) {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
String sql = ms.getBoundSql(invocation.getArgs()[1]).getSql().replaceAll("\\s+", " ");
System.err.printf("[慢SQL] [%dms] %s\nSQL: %s\n", duration, ms.getId(), sql);
}
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this); // 动态代理包裹
}
@Override
public void setProperties(Properties properties) {
if (properties.containsKey("threshold")) {
this.threshold = Long.parseLong(properties.getProperty("threshold"));
}
}
}
插件之间传递数据的方式(插件上下文)
技巧一:ThreadLocal
public class PluginContext {
public static final ThreadLocal<String> currentTenant = new ThreadLocal<>();
}
技巧二:MetaObject(操作对象属性)
MetaObject metaObject = SystemMetaObject.forObject(resultObject);
metaObject.setValue("phone", AESUtil.decrypt(metaObject.getValue("phone")));
四、插件执行顺序控制
Spring 注入的顺序 == 插件的执行顺序
想显式控制顺序可使用 @Order
注解(或 Ordered
接口)
@Bean
@Order(1)
public Interceptor pluginA() {}
@Bean
@Order(2)
public Interceptor pluginB() {}
五、插件可能产生的问题
问题 | 原因与解决方案 |
---|---|
插件无效 | 未注册为 Bean 或未使用 @Intercepts 注解 |
插件顺序影响执行逻辑 | 插件有状态,注册顺序不当 |
反射效率低 | 建议缓存反射字段,或使用 MetaObject 操作属性 |
插件方法拦截过多,影响性能 | 精准拦截必要接口,避免滥用 |
插件链不生效 | 被其他中间件(如分页插件)提前消费 |
六、总结
MyBatis 插件是强大但也容易踩坑的机制,理解其执行原理、注册方式与生命周期,是安全有效使用的基础。
如果本文对你有帮助,欢迎点赞、评论、收藏!我是 李卷卷,专注Java相关知识输出。感谢阅读!
正文完