MyBatis 的拦截器是一个十分强大的特性,它可以让我们在 MyBatis 调用数据库操作的过程中插入自己的逻辑,非常适合做一些数据操作的审计、性能优化、事务管理、执行日志输出等。
拦截器的触发策略
拦截接口
MyBatis 允许拦截 SQL 生命周期中的四个关键节点:Executor 、ParameterHandler 、ResultSetHandler 、StatementHandler ,它们在数据库操作中扮演核心角色,Mybatis 提供了在这四个对象执行前后插入自定义逻辑的强大支持。通常情况下,如果定义的所有接口的拦截器,拦截顺序大致如下:
- StatementHandler
-
- 在 MyBatis 准备执行 SQL 之前,首先会创建 Statement 对象,这时会触发对 StatementHandler 的拦截。
- 使用 StatementHandler 拦截器可以在 SQL 语句被发送到数据库执行前进行自定义操作,比如修改原始 SQL 语句、设置特殊的 Statement 属性等。
- ParameterHandler
-
- 在 Statement 准备执行之前,ParameterHandler 将会被调用,以设置SQL语句中的参数。
- 通过拦截 ParameterHandler,可以在 SQL 参数绑定前后进行操作。适用于复杂的参数处理逻辑,比如加密/解密数据,或者对特殊的参数格式进行处理。
- Executor
-
- 执行器 Executor 是整个执行过程的中心,它会调用上述的 StatementHandler 和 ParameterHandler 来准备命令并执行。
- 拦截 Executor ,可以在 SQL 执行前后添加逻辑,比如缓存的逻辑,在查询语句执行前后检查和添加缓存。
- ResultSetHandler
-
- SQL 语句执行后,如果有结果集返回,MyBatis 将使用 ResultSetHandler 来处理这些结果集,将 JDBC 返回的 ResultSet 转化为 MyBatis 中指定的结果对象。
- 拦截 ResultSetHandler 支持在结果集映射过程中插入自定义逻辑,比如结果集的加工处理、性能统计等。
如果定义了所有这些拦截器,它们将会按照上面的顺序被触发。但是,拦截器的触发会根据具体的执行操作来调整。例如,如果SQL执行不涉及结果集的处理(如插入、更新或删除操作),ResultSetHandler将不会被触发。同样,如果在Executor拦截器中终止了SQL执行,随后的拦截器也不会再被触发。
拦截方法
上述四个核心接口提供了多个精细化方法,允许在数据库操作的不同阶段进行精确的干预和拦截。
Executor:
update
:负责执行 insert、update、delete 三种类型的 SQL 语句。query
:负责执行 select 类型的 SQL 语句。queryCursor
:负责执行 select 类型的 SQL 语句,返回 Cursor 对象。flushStatements
:提交批处理语句,返回批处理结果。commit
:提交事务。rollback
:回滚事务。getTransaction
:获取事务对象。close
:关闭 executor,同时根据参数决定是否强制回滚未提交的事务。isClosed
:检查 executor 是否已经关闭。clearLocalCache
:清除本地缓存。
StatementHandler:
prepare
:准备一个数据库 Statement 对象以待执行。这个方法根据配置和上下文信息来创建一个 PreparedStatement 或 CallableStatement 对象。parameterize
:在 SQL 语句被执行之前,该方法负责将 SQL 参数设置到 PreparedStatement 对象中。batch
:负责处理批量执行的逻辑,将多个更新语句作为一个批处理提交。update
:执行写操作(insert、update、delete)的 SQL 语句。query
:执行查询操作(select)的 SQL 语句,并返回结果。queryCursor
:负责执行查询操作(select)SQL 语句,返回 Cursor 对象。getBoundSql
:返回 BoundSql 对象,这个对象包含了要执行的 SQL 语句以及该语句中所需的参数信息。
ParameterHandler:
getParameterObject
:此方法用于获取 SQL 参数对象。setParameters
:此方法将 SQL 命令中的参数与实际的参数对象相匹配。它负责将传入的参数设置到 PreparedStatement 中。
ResultSetHandler:
handleResultSets
:这是主要的方法之一,它接受一个 Statement 对象作为参数,并将 SQL执行的结果 ResultSet 映射到结果对象。handleOutputParameters
:当存储过程调用完成之后,这个方法会处理其输出参数。它同样接受一个 Statement 对象作为参数。
简单的拦截器demo
步骤
默认已引入 Mybatis 相关依赖。
- 创建一个实现了 MyBatis 提供的
Interceptor
接口的类,这个接口包含一个方法intercept(Invocation invocation)
。
- 在
intercept
方法里,通过invocation
对象可以获取执行的目标方法,你可以在执行目标方法之前或之后加入自己的业务逻辑代码。 - 使用
@Intercepts
和@Signature
注解来配置拦截器,指明想要拦截的接口和方法;关于@Signature
注解下文详述。
java
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class ExampleInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 在执行方法前你可以添加你自己的逻辑
// 执行原方法
Object returnObject = invocation.proceed();
// 在执行方法后你可以添加你自己的逻辑
return returnObject;
}
}
- 注册拦截器,下文详述。
注册 Mybatis 拦截器
在 Spring 框架中,如果创建了一个拦截器类但没有将其注册为 Spring Bean,那么这个拦截器不会自动被 MyBatis 检测到和使用,导致拦截器失效。为了让拦截器生效,需要在配置中明确声明并注册这个拦截器。
Spring
在使用 Spring 配置 MyBatis 时,一般有两种方式注册拦截器:
- XML 配置:
xml
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="typeAliasesPackage" value="com.example.model" />
<property name="plugins">
<array>
<bean class="com.example.MyInterceptor"/>
</array>
</property>
</bean>
- Configuration 配置类:
java
@Configuration
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setTypeAliasesPackage("com.example.model");
// 创建并添加拦截器
Interceptor interceptor = new MyInterceptor();
sessionFactory.setPlugins(new Interceptor[]{interceptor});
return sessionFactory.getObject();
}
}
Spring Boot
Spring Boot 通过自动配置简化了 MyBatis 的配置过程。同样有两种方式注册拦截器:
- 在 Spring Boot 中注册 MyBatis 拦截器通常是通过编写配置类完成的。
java
@Configuration
public class MybatisConfig {
@Bean
public Interceptor myInterceptor() {
return new MyInterceptor();
}
// 非必需,用于更复杂的拦截器链配置,比如控制多个拦截器的加载顺序
@Bean
public ConfigurationCustomizer mybatisConfigurationCustomizer() {
return new ConfigurationCustomizer() {
@Override
public void customize(org.apache.ibatis.session.Configuration configuration) {
configuration.addInterceptor(myInterceptor());
}
};
}
}
- 在 Spring Boot 2.x 以后的版本中,将拦截器类定义为 Spring 组件(使用 @Component 等注解),可以不需要手动注册它们,Spring Boot 的自动配置将会自动扫描并注册它们。
java
@Component
public class MyInterceptor implements Interceptor {
// 实现拦截器逻辑
}
@Signature注解
@Signature
注解用于定义在 MyBatis 插件中拦截的目标方法。当你创建一个 MyBatis 拦截器时,该注解指定插件将拦截的接口、方法名以及方法的参数类型。
@Signature
注解通常与 @Intercepts
注解配合使用,@Intercepts
注解用来注解一个类,而 @Signature
则在 @Intercepts
注解的 signature
属性数组中使用。可以在单个插件中指定多个 @Signature
,这意味着拦截器可拦截多个不同的点。
一个 @Signature
注解包含以下三个参数:
- type :指定要拦截的 MyBatis 接口,即上文介绍的拦截接口的 Class 对象。比如,
Executor.class
,ParameterHandler.class
,ResultSetHandler.class
或StatementHandler.class
。 - method:指定要拦截的方法名。它是你要插入自定义行为的 MyBatis 接口方法的名称。
- args:指定要拦截的方法的参数类型列表。它是一个 Class 类型的数组,确保你按正确的顺序提供了方法的参数类型。
例如 Mybatis Plus 的拦截器源码是这样定义的:
java
@Intercepts({@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class}
), @Signature(
type = StatementHandler.class,
method = "getBoundSql",
args = {}
), @Signature(
type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class}
), @Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
), @Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
)})
public class MybatisPlusInterceptor implements Interceptor {
//...
}