MyBatis 拦截器完整使用指南
MyBatis 拦截器完整使用指南
引言
MyBatis 拦截器(Interceptor)是 MyBatis 强大的扩展机制之一,它允许我们在 SQL 语句执行的不同阶段插入自定义逻辑。通过拦截器,我们可以实现分页插件、权限校验、SQL 日志、自动填充等功能。本文将深入讲解 MyBatis 拦截器的原理、使用场景、编写方法以及实际应用。
一、拦截器的工作原理
1.1 拦截器机制概述
MyBatis 的拦截器基于责任链设计模式实现,属于高级别的扩展机制。它可以在 SQL 执行的四个关键节点进行拦截和处理:
- 参数预处理(ParameterHandler):在执行 SQL 之前,对参数进行预处理
- SQL 改写(StatementHandler):拦截 JDBC Statement 对象,用于修改 SQL 语句
- 语句执行(PreparedStatement):在执行 Statement 之前进行拦截
- 结果集转换(ResultSetHandler):处理查询结果,可用于分页、结果加密等操作
1.2 核心组件关系
Executor → StatementHandler → PreparedStatement → Statement
↓ ↓ ↓ ↓
Interceptor Interceptor Interceptor Interceptor
当 MyBatis 执行数据库操作时,会按照上述顺序依次经过各个拦截点。每个拦截器都实现了 `Interceptor` 接口,并可以在 `@Intercepts` 注解中声明要拦截的签名。
二、拦截器的使用场景
2.1 分页插件
最经典的应用场景就是分页功能。PageHelper 等分页插件就是通过拦截器拦截 SQL 语句,动态添加 `LIMIT` 子句来实现的。
2.2 数据权限控制
拦截器可以读取当前用户信息,自动在查询条件中添加权限过滤,如数据行级权限、部门隔离等。
2.3 自动填充
实现创建时间、更新时间、创建人、更新人等字段的自动填充功能。
2.4 SQL 日志记录
拦截 SQL 语句和执行参数,记录完整的 SQL 日志,便于调试和性能分析。
2.5 性能优化
通过拦截器实现 SQL 缓存、结果集缓存等功能。
三、编写自定义拦截器
3.1 实现基本结构
创建拦截器需要实现 MyBatis 的 `Interceptor` 接口,该接口包含三个核心方法:
“`java
@Intercepts({
@Signature(
type = Executor.class,
method = “query”,
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)
})
public class MyInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 拦截处理逻辑
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 属性配置
}
}
3.2 方法详解
intercept 方法:这是拦截器的核心方法,所有拦截逻辑都在这里实现。`invocation` 对象包含了目标对象、方法和参数,调用 `invocation.proceed()` 可以继续执行原始方法。
plugin 方法:MyBatis 使用 JDK 动态代理来实现拦截。此方法返回目标对象的代理对象,只有被代理的对象上的方法才会被拦截。
setProperties 方法:当拦截器被配置在 `mybatis-config.xml` 中时,可以传入自定义属性,在此方法中接收和解析这些属性。
3.3 完整的拦截器示例
以下是一个实现 SQL 日志和性能分析的拦截器示例:
java
@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 PerformanceInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(PerformanceInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
long startTime = System.currentTimeMillis();
String sql = getSql(invocation);
logger.info(“执行 SQL: {}”, sql);
Object result = invocation.proceed();
long duration = System.currentTimeMillis() – startTime;
logger.info(“SQL 执行耗时:{} ms”, duration);
if (duration > 1000) {
logger.warn(“SQL 执行耗时超过 1 秒:{} ms”, duration);
}
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 处理配置属性
}
private String getSql(Invocation invocation) {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
String sql = ms.getBoundSql(invocation.getArgs()[1]).getSql();
return sql.replace(“\n”, “”).replace(“\r”, “”).replaceAll(“\\s+”, ” “);
}
}
四、实际应用场景
4.1 分页拦截器实现
java
@Intercepts({
@Signature(
type = StatementHandler.class,
method = “prepare”,
args = {Connection.class, Integer.class}
)
})
public class SimplePaginationInterceptor implements Interceptor {
private PageContext pageContext = new PageContext();
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = statementHandler.getBoundSql();
Object paramObj = boundSql.getParameters();
Map
if (paramObj instanceof Map) {
paramMap = (Map
} else if (paramObj instanceof PageContext) {
pageContext = (PageContext) paramObj;
paramMap = ((PageContext) paramObj).getParams();
}
if (pageContext != null && pageContext.isPage()) {
String sql = boundSql.getSql();
String countSql = countSql(sql);
// 添加分页 SQL
String pageSql = addPagination(sql, pageContext);
boundSql.setSql(pageSql);
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
private String countSql(String sql) {
// 将 SELECT 语句转换为 COUNT 查询
return sql.replaceAll(“SELECT.* FROM”, “SELECT COUNT(*) FROM”, true);
}
private String addPagination(String sql, PageContext pageContext) {
return sql + ” LIMIT ” + pageContext.getOffset() + “,” + pageContext.getLimit();
}
}
4.2 数据权限拦截器
java
@Intercepts({
@Signature(
type = StatementHandler.class,
method = “prepare”,
args = {Connection.class, Integer.class}
)
})
public class DataPermissionInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler handler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
Object parameter = boundSql.getParameters();
// 获取当前用户信息
UserInfo currentUser = SecurityContext.getCurrentUser();
// 根据用户权限添加数据过滤条件
String whereClause = generateWhereClause(currentUser);
if (whereClause != null && !whereClause.isEmpty()) {
sql = addWhereCondition(sql, whereClause);
}
boundSql.setSql(sql);
return invocation.proceed();
}
private String generateWhereClause(UserInfo user) {
if (user.getDepartmentId() != null) {
return String.format(” AND department_id = %d”, user.getDepartmentId());
}
return “”;
}
private String addWhereCondition(String sql, String condition) {
int index = sql.toUpperCase().indexOf(” WHERE “);
if (index == -1) {
return sql + ” WHERE 1=1 ” + condition;
}
// 复杂场景需要更精细的 SQL 解析
return sql + ” AND 1=1″ + condition;
}
}
4.3 自动填充拦截器
java
@Intercepts({
@Signature(
type = ParameterHandler.class,
method = “setParameters”,
args = {PreparedStatement.class}
)
})
public class AutoFillInterceptor implements Interceptor {
private static final String UPDATE_TIME = “updateTime”;
private static final String CREATE_TIME = “createTime”;
private static final String UPDATE_USER = “updateUser”;
private static final String CREATE_USER = “createUser”;
@Override
public Object intercept(Invocation invocation) throws Throwable {
ParameterHandler handler = (ParameterHandler) invocation.getTarget();
PreparedStatement statement = (PreparedStatement) invocation.getArgs()[0];
BoundSql boundSql = handler.getBoundSql();
Object parameter = boundSql.getParameters();
if (shouldAutoFill(parameter)) {
fillValue(parameter);
}
return invocation.proceed();
}
private boolean shouldAutoFill(Object parameter) {
if (parameter instanceof Map) {
Map, ?> paramMap = (Map, ?>) parameter;
return paramMap.containsKey(“updateTime”) || paramMap.containsKey(“createTime”);
}
return false;
}
private void fillValue(Object parameter) {
Map
// 设置时间
paramMap.put(UPDATE_TIME, new Date());
if (!paramMap.containsKey(CREATE_TIME)) {
paramMap.put(CREATE_TIME, new Date());
}
// 设置用户
String currentUser = SecurityContext.getCurrentUser().getUsername();
paramMap.put(UPDATE_USER, currentUser);
if (!paramMap.containsKey(CREATE_USER)) {
paramMap.put(CREATE_USER, currentUser);
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 配置属性
}
}
五、配置与注册
5.1 MyBatis 配置文件
在 `mybatis-config.xml` 中注册拦截器:
xml
5.2 Spring Boot 配置
如果使用 Spring Boot,可以通过配置类注册拦截器:
java
@Configuration
public class MyBatisConfig {
@Bean
public Interceptor pageInterceptor() {
return new PageInterceptor();
}
@Bean
public Interceptor performanceInterceptor() {
return new PerformanceInterceptor();
}
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(pageInterceptor());
interceptor.addInnerInterceptor(performanceInterceptor());
return interceptor;
}
}
“`
六、注意事项与最佳实践
6.1 性能考虑
- 拦截器会在每次 SQL 执行时调用,避免在 `intercept` 方法中执行耗时操作
- 使用异步日志或批量记录来减少性能影响
- 对于高并发场景,考虑使用缓存减少重复计算
6.2 错误处理
- 在拦截器中妥善处理异常,避免影响正常的业务逻辑
- 记录详细的错误日志,便于问题定位
- 避免在拦截器中抛出未处理的异常
6.3 配置管理
- 将拦截器的配置参数外部化,便于根据不同环境调整
- 使用配置文件管理拦截器开关,方便启用或禁用特定功能
- 为每个拦截器添加独立的配置命名空间
6.4 版本兼容性
- 注意 MyBatis 版本之间的 API 变化
- 使用稳定的 API 签名,避免内部实现的变更
- 定期测试拦截器在升级后的兼容性
七、总结
MyBatis 拦截器是强大的扩展机制,通过理解其工作原理并合理应用,可以解决许多常见的问题。无论是分页、权限控制、性能监控还是自动填充,拦截器都能提供优雅的解决方案。
记住以下要点:
- 选择合适的拦截点:根据需求选择 `Executor`、`StatementHandler` 等拦截点
- 保持拦截器轻量:避免在拦截器中执行耗时操作
- 良好的错误处理:确保拦截器异常不影响业务逻辑
- 合理的配置管理:使用配置文件管理拦截器参数
- 充分测试:在不同场景下测试拦截器的功能和性能
- [MyBatis 官方文档](https://mybatis.org/mybatis-3/zh/configuration.html)
- [PageHelper 分页插件](https://pagehelper.gitee.io/pagehelper/)
- [MyBatis-Plus 文档](https://baomidou.com/pages/221785/#interceptor)
掌握 MyBatis 拦截器,你将能够更灵活地扩展和定制 MyBatis 的行为,满足各种复杂的业务需求。
—
参考资料:



发表评论