MyBatis 插件开发的完整详细例子

MyBatis 插件(Interceptor)允许开发者在已映射语句执行过程中的某一点进行拦截调用,从而实现自定义逻辑。以下是一个完整的 MyBatis 插件开发示例,涵盖所有使用场景,并附有详细注释和总结。
1. MyBatis 插件基础
MyBatis 允许拦截以下接口的方法:
- Executor :
update
,query
,flushStatements
,commit
,rollback
,getTransaction
,close
,isClosed
- ParameterHandler :
getParameterObject
,setParameters
- ResultSetHandler :
handleResultSets
,handleCursorResultSets
,handleOutputParameters
- StatementHandler :
prepare
,parameterize
,batch
,update
,query
2. 插件开发示例
2.1. 自定义插件类
创建一个自定义插件类 MyPlugin
,该插件将拦截 Executor
的 query
方法和 StatementHandler
的 prepare
方法。
java
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.statement.StatementHandler;
import java.sql.Connection;
import java.sql.Statement;
import java.util.Properties;
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class MyPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取被拦截方法的参数
Object[] args = invocation.getArgs();
// 拦截 Executor.query 方法
if (invocation.getTarget() instanceof Executor) {
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
BoundSql boundSql = ms.getBoundSql(parameter);
String sql = boundSql.getSql();
System.out.println("Executing SQL: " + sql);
// 执行原方法
return invocation.proceed();
}
// 拦截 StatementHandler.prepare 方法
if (invocation.getTarget() instanceof StatementHandler) {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
Connection connection = (Connection) args[0];
Integer integer = (Integer) args[1];
// 获取原始 SQL
BoundSql boundSql = statementHandler.getBoundSql();
String originalSql = boundSql.getSql();
System.out.println("Original SQL: " + originalSql);
// 修改 SQL(例如添加注释)
String newSql = "/* MyPlugin */ " + originalSql;
BoundSql newBoundSql = new BoundSql(
boundSql.getMappedStatement().getConfiguration(),
newSql,
boundSql.getParameterMappings(),
boundSql.getParameterObject()
);
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
metaObject.setValue("delegate.boundSql", newBoundSql);
// 执行原方法
return invocation.proceed();
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
// 使用 Plugin.wrap 包装目标对象
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 设置插件属性(可选)
}
}
2.2. 配置插件
在 mybatis-config.xml
中配置自定义插件:
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置插件 -->
<plugins>
<plugin interceptor="com.example.plugin.MyPlugin">
<!-- 可以设置插件属性 -->
<!-- <property name="propertyName" value="propertyValue"/> -->
</plugin>
</plugins>
<!-- 其他配置... -->
</configuration>
2.3. 测试插件
编写测试代码来验证插件的功能:
java
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.Reader;
public class PluginTest {
public static void main(String[] args) throws Exception {
// 读取 MyBatis 配置文件
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
// 执行查询操作,触发插件拦截
User user = mapper.selectUserById(1L);
System.out.println("User: " + user.getName());
}
}
}
3. 核心使用场景
3.1. 日志记录
在 Executor
的 query
和 update
方法中添加日志记录,便于调试和监控。
3.2. SQL 修改
在 StatementHandler
的 prepare
方法中修改 SQL 语句,例如添加统一的注释或进行性能优化。
3.3. 参数处理
在 ParameterHandler
的 setParameters
方法中对参数进行预处理,如加密、格式化等。
3.4. 结果集处理
在 ResultSetHandler
的 handleResultSets
方法中对结果集进行后处理,如数据脱敏、缓存等。
4. 表格整理总结
场景 | 拦截接口及方法 | 具体应用 | 示例代码片段 |
---|---|---|---|
日志记录 | Executor.query , Executor.update |
在执行 SQL 前后记录日志 | System.out.println("Executing SQL: " + sql); |
SQL 修改 | StatementHandler.prepare |
修改或优化 SQL 语句 | String newSql = "/* MyPlugin */ " + originalSql; |
参数处理 | ParameterHandler.setParameters |
对参数进行预处理(如加密) | preparedStatement.setString(1, encrypt(parameter)); |
结果集处理 | ResultSetHandler.handleResultSets |
对查询结果进行后处理(如脱敏) | resultList.forEach(item -> item.setEmail(maskEmail(item.getEmail()))); |
性能监控 | Executor.query , Executor.update |
记录 SQL 执行时间 | long startTime = System.currentTimeMillis(); ... long endTime = ... |
分页支持 | StatementHandler.parameterize |
动态添加分页参数 | preparedStatement.setInt(1, offset); preparedStatement.setInt(2, limit); |
5. 最佳实践建议
- 理解底层行为:在重写方法时,需理解其底层行为,避免破坏 MyBatis 的核心功能。
- 谨慎修改 SQL:修改 SQL 时要确保语法正确,避免引入潜在的 SQL 注入风险。
- 合理使用属性 :通过
setProperties
方法为插件设置属性,增强灵活性。 - 单元测试:编写单元测试验证插件功能,确保其在各种场景下的稳定性。
- 文档记录:详细记录插件的使用方法和注意事项,便于团队成员理解和维护。
通过以上示例和总结,可以全面掌握 MyBatis 插件的开发和应用场景。