在 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. 插件的工作机制
- MyBatis 在运行时会为核心对象创建一个 代理对象(Proxy)。
- 代理对象会调用插件中的
intercept()
方法。 - 如果插件链中存在多个插件,MyBatis 会通过 责任链模式 依次调用这些插件。
- 在插件的
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. 拦截对象及其拦截方法
对象 | 常用拦截方法 | 说明 |
---|---|---|
Executor | update , query , commit , rollback | 拦截增删改查操作 |
StatementHandler | prepare , parameterize , batch | 拦截 SQL 语句生成和执行 |
ParameterHandler | getParameterObject , setParameters | 拦截 SQL 参数的处理 |
ResultSetHandler | handleResultSets , 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 的功能 😎