MyBatis自定义拦截器
一、概述
在实际开发中,我们常常需要在不侵入业务代码的前提下,对SQL执行过程进行增强处理,比如SQL耗时统计、参数加密、结果集脱敏、数据权限过滤等场景。MyBatis提供的自定义拦截器(Plugin)机制,正是为这类需求而生。
MyBatis拦截器基于责任链模式和动态代理实现,允许开发者在SQL执行的关键节点插入自定义逻辑,无需修改原有Mapper接口、XML映射文件或业务代码,就能实现功能增强。其核心优势在于低耦合、高复用,广泛应用于日志记录、性能监控、数据安全等场景,是MyBatis生态中极具灵活性的扩展机制。
二、自定义MyBatis拦截器
先实现一个标准的MyBatis拦截器(以「SQL耗时统计」为例),后续以此为例配置:
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.mapping.MappedStatement;
import java.util.Properties;
/**
* 示例拦截器:统计SQL执行耗时
*/
// 拦截Executor的query/update方法(可根据需求替换拦截点)
@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 SqlCostInterceptor implements Interceptor {
// 核心拦截逻辑
@Override
public Object intercept(Invocation invocation) throws Throwable {
long startTime = System.currentTimeMillis();
try {
// 执行原SQL方法
return invocation.proceed();
} finally {
// 统计耗时
long cost = System.currentTimeMillis() - startTime;
System.out.println("SQL执行耗时:" + cost + "ms");
}
}
// 包装目标对象(MyBatis自动调用,推荐用Plugin.wrap标准写法)
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
// 读取拦截器配置属性(可选)
@Override
public void setProperties(Properties properties) {
// 示例:读取配置的"logLevel"属性
String logLevel = properties.getProperty("logLevel", "INFO");
System.out.println("拦截器配置日志级别:" + logLevel);
}
}
三、注册拦截器到SqlSessionFactory
Spring Boot中无需XML配置,通过@Configuration配置类注册SqlSessionFactory,并将拦截器注入其中。
3.1 方式1:基础版(单拦截器,无自定义属性)
适合拦截器无需配置属性、仅需简单注册的场景:
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
@MapperScan("com.yourpackage.mapper") // 替换Mapper接口包路径
public class MyBatisConfig {
/**
* 配置SqlSessionFactory,注册拦截器
*/
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
// 1. 设置数据源(Spring Boot自动配置的数据源,直接注入)
sessionFactoryBean.setDataSource(dataSource);
// 2. 可选:加载Mapper.xml(注解版Mapper可省略)
sessionFactoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/**/*.xml") // 替换Mapper.xml路径
);
// 3. 核心:创建拦截器实例并注册
SqlCostInterceptor sqlCostInterceptor = new SqlCostInterceptor();
sessionFactoryBean.setPlugins(sqlCostInterceptor); // 单拦截器直接传入
// 4. 可选:MyBatis全局配置(如驼峰命名、日志等)
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true); // 下划线转驼峰
sessionFactoryBean.setConfiguration(configuration);
return sessionFactoryBean.getObject();
}
}
3.2 方式2:进阶版(多拦截器+自定义属性)
适合需要注册多个拦截器、或给拦截器配置自定义属性的场景(如加密/解密拦截器):
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
import java.util.Properties;
@Configuration
@MapperScan("com.yourpackage.mapper")
public class MyBatisConfig {
// ========== 1. 声明拦截器 Bean(便于依赖注入、属性配置) ==========
@Bean
public SqlCostInterceptor sqlCostInterceptor() {
SqlCostInterceptor interceptor = new SqlCostInterceptor();
// 给拦截器设置自定义属性(对应intercept.setProperties方法)
Properties props = new Properties();
props.setProperty("logLevel", "DEBUG");
interceptor.setProperties(props);
return interceptor;
}
// 示例:加密拦截器
@Bean
public EncryptParameterInterceptor encryptParameterInterceptor() {
return new EncryptParameterInterceptor();
}
// 示例:解密拦截器
@Bean
public DecryptResultSetInterceptor decryptResultSetInterceptor() {
return new DecryptResultSetInterceptor();
}
// ========== 2. 注册拦截器到 SqlSessionFactory ==========
@Bean
public SqlSessionFactory sqlSessionFactory(
DataSource dataSource,
SqlCostInterceptor sqlCostInterceptor,
EncryptParameterInterceptor encryptInterceptor,
DecryptResultSetInterceptor decryptInterceptor
) throws Exception {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
// 可选:加载Mapper.xml
sessionFactoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")
);
// 核心:注册多个拦截器(数组形式传入)
sessionFactoryBean.setPlugins(sqlCostInterceptor, encryptInterceptor, decryptInterceptor);
return sessionFactoryBean.getObject();
}
}
3.3 方式3:极简版(仅Spring Boot+注解版MyBatis)
若项目完全使用注解版Mapper(无XML),且拦截器无自定义属性,可简化配置:
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
@MapperScan("com.yourpackage.mapper")
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
// 直接注册拦截器
sessionFactoryBean.setPlugins(
new SqlCostInterceptor(),
new EncryptParameterInterceptor(),
new DecryptResultSetInterceptor()
);
return sessionFactoryBean.getObject();
}
}
四、关键注意事项
4.1 拦截器执行顺序
MyBatis拦截器遵循责任链模式:setPlugins(拦截器A, 拦截器B, 拦截器C) 中,注册顺序越靠前,执行顺序越靠后;例如:注册顺序为「加密拦截器 → 解密拦截器 → 耗时统计拦截器」,执行时顺序为「耗时统计 → 解密 → 加密」;
若拦截不同接口(如ParameterHandler/ResultSetHandler/Executor),顺序不影响;若拦截同一接口,需按业务逻辑调整。
4.2 拦截器生效的前提
@MapperScan必须扫描到你的Mapper接口(否则Mapper无法被MyBatis管理,拦截器不生效);- 拦截器的
@Intercepts注解必须正确:type(拦截接口)、method(拦截方法)、args(方法参数类型)需与MyBatis源码完全匹配; SqlSessionFactory必须被Spring扫描到(配置类加@Configuration)。
4.3 常见拦截点参考
| 拦截接口 | 拦截方法 | 适用场景 |
|---|---|---|
| Executor | query/update | SQL执行耗时、分页、数据权限 |
| ParameterHandler | setParameters | 参数加密、参数校验 |
| ResultSetHandler | handleResultSets | 结果集解密、数据脱敏 |
| StatementHandler | prepare | SQL改写、SQL日志打印 |
4.4 调试拦截器是否生效
- 在拦截器的
intercept方法中加System.out.println或日志,执行Mapper方法查看是否输出; - 打断点:在
intercept方法中打断点,调试执行Mapper方法,确认是否进入拦截逻辑; - 检查
Spring上下文:启动项目后,查看SqlSessionFactory Bean是否包含你的拦截器(可通过Spring Boot Actuator或调试模式查看)。
五、常见问题排查
5.1 拦截器不生效
- 检查
@Intercepts注解的type/method/args是否正确(参数类型必须是全限定类名); - 检查
SqlSessionFactory是否注册了拦截器(断点查看sessionFactoryBean.getPlugins()); - 检查
Mapper接口是否被@MapperScan扫描(未扫描则MyBatis不会处理该Mapper)。
5.2 拦截器执行顺序错误
- 调整
setPlugins中拦截器的传入顺序(注册顺序与执行顺序相反); - 若需精准控制,可自定义
InterceptorChain,但不推荐(破坏MyBatis原生逻辑)。
5.3 反射报错(如字段不可访问)
- 在拦截器中处理实体字段时,需调用
field.setAccessible(true); - 避免拦截
String/Integer等基础类型(无字段可反射)。
通过以上配置,即可在Spring Boot中灵活配置MyBatis拦截器,适配单/多拦截器、注解/XML版MyBatis、自定义属性等场景。

浙公网安备 33010602011771号