MyBatis 拦截器的工作原理
MyBatis 的拦截器是一个十分强大的特性,它可以让我们在 MyBatis 调用数据库操作的过程中插入自己的逻辑,非常适合做一些数据操作的审计、性能优化、事务管理、执行日志输出等。
MyBatis 的拦截器机制基于 AOP(面向切面编程),允许在执行 SQL 语句前后插入自定义逻辑。MyBatis 提供了几个内置的拦截点,这些拦截点对应于 MyBatis 内部组件的不同阶段。
拦截器可以用来修改 SQL 语句、参数、结果集等。
MyBatis 拦截器的工作流程
1. 初始化阶段:
- 当 MyBatis 初始化时,它会读取配置文件中的 `<plugins>` 配置,加载并实例化所有声明的拦截器。
- 拦截器的 `setProperties()` 方法会被调用以设置拦截器的属性。
2. 拦截器链构建:
- MyBatis 根据配置文件中的 `<plugins>` 配置创建拦截器链。
- 每个拦截器都会被包装进 `Plugin` 对象中,并通过 `Plugin.wrap()` 方法与目标对象(如 `Executor` 实现)连接起来。
3. 执行阶段:
- 当执行 SQL 语句时,请求会通过拦截器链传递。
- 每个拦截器的 `intercept()` 方法会被调用,如果 `intercept()` 方法返回结果,则请求不再传递给下一个拦截器或目标对象。
- 如果没有返回结果,请求会继续传递给下一个拦截器或目标对象。
- 请求最终到达目标对象(如 `Executor` 实现)并执行。
- 执行完成后,结果会反向通过拦截器链,再次调用每个拦截器的 `intercept()` 方法。
示例:
这里是一个简单的 `Executor` 拦截器示例,用于记录 SQL 执行的时间:
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 java.util.Properties;
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class LoggingInterceptor implements Interceptor {
private long startTime;
@Override
public Object intercept(Invocation invocation) throws Throwable {
startTime = System.currentTimeMillis();
// 记录 SQL 执行前的日志
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
BoundSql boundSql = ms.getBoundSql(invocation.getArgs()[1]);
String sql = boundSql.getSql();
System.out.println("Executing SQL: " + sql);
// 继续执行原始方法
Object result = invocation.proceed();
// 记录 SQL 执行后的日志
long endTime = System.currentTimeMillis();
System.out.println("SQL executed in " + (endTime - startTime) + "ms");
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 设置拦截器的属性
}
}
@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 LoggingInterceptor ();
sessionFactory.setPlugins(new Interceptor[]{interceptor});
return sessionFactory.getObject();
}
}
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
:清除本地缓存。
Executor(执行器拦截器):
- 用途:拦截MyBatis执行器方法的执行。
- 使用:允许拦截和自定义MyBatis执行器的行为。例如,可以添加缓存、日志记录或审计功能到执行器中。这些拦截器可以在MyBatis执行的不同阶段扩展或修改其行为。您可以通过实现MyBatis提供的相应接口并在MyBatis配置文件中进行配置来实现这些拦截器。
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 语句以及该语句中所需的参数信息。
StatementHandler(语句拦截器):
-- 用途:拦截SQL语句的执行。
- 使用:可以在SQL语句执行之前修改或增强它们。例如,可以向WHERE子句添加额外的条件或记录执行的语句。分页等
ParameterHandler:
getParameterObject
:此方法用于获取 SQL 参数对象。setParameters
:此方法将 SQL 命令中的参数与实际的参数对象相匹配。它负责将传入的参数设置到 PreparedStatement 中。
ParameterHandler(参数拦截器):
- 用途:拦截SQL语句的参数设置。
- 使用:允许在将参数设置到SQL语句之前修改或验证它们。例如,可以对作为参数传递的敏感信息进行加密或解密。
ResultSetHandler:
handleResultSets
:这是主要的方法之一,它接受一个 Statement 对象作为参数,并将 SQL执行的结果 ResultSet 映射到结果对象。handleOutputParameters
:当存储过程调用完成之后,这个方法会处理其输出参数。它同样接受一个 Statement 对象作为参数。
ResultHandler(结果集拦截器):
- 用途:拦截从SQL语句返回的结果集的处理。
- 使用:可以在将结果集返回给应用程序之前修改或分析它们。例如,可以对结果集数据进行转换或执行额外的计算。
示例
步骤
默认已引入 Mybatis 相关依赖。
- 创建一个实现了 MyBatis 提供的
Interceptor
接口的类,这个接口包含一个方法intercept(Invocation invocation)
。 - 在
intercept
方法里,通过invocation
对象可以获取执行的目标方法,你可以在执行目标方法之前或之后加入自己的业务逻辑代码。 - 使用
@Intercepts
和@Signature
注解来配置拦截器,指明想要拦截的接口和方法;关于@Signature
注解下文详述。 - 注册拦截器,下文详述。
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 配置:
<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 配置类:
@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 拦截器通常是通过编写配置类完成的。
@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 的自动配置将会自动扫描并注册它们。
@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 的拦截器源码是这样定义的:
@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 {
//...
}
MyBatis 内置7种拦截器类型及拦截时机
- **Executor 拦截器**:在执行 SQL 语句之前和之后。
- **ParameterHandler 拦截器**:在设置参数之前和之后。
- **ResultSetHandler 拦截器**:在处理结果集之前和之后。
- **StatementHandler 拦截器**:在创建和执行 SQL 语句之前和之后。
- **Environment 拦截器**:在创建环境配置之前和之后。
- **TransactionFactory 拦截器**:在创建事务之前和之后。
- **TypeHandlerRegistry 拦截器**:在注册类型处理器之前和之后。
1. **Executor 拦截器**
- **类型**:`Executor`
- **作用**:拦截 `Executor` 接口的方法,包括 `query`, `update`, `delete`, `insert` 等。
- **拦截时机**:
- **`query` 方法**:在执行 SQL 查询之前和之后。
- **`update`, `delete`, `insert` 方法**:在执行 DML(数据操纵语言)操作之前和之后。
```java
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
```
2. **ParameterHandler 拦截器**
- **类型**:`ParameterHandler`
- **作用**:拦截参数处理器,用于处理预编译 SQL 语句的参数绑定。
- **拦截时机**:
- 在 `PreparedStatement` 设置参数之前和之后。
```java
@Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})
```
3. **ResultSetHandler 拦截器**
- **类型**:`ResultSetHandler`
- **作用**:拦截结果集处理器,用于处理从数据库获取的结果集。
- **拦截时机**:
- 在处理结果集之前和之后。
```java
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
```
4. **StatementHandler 拦截器**
- **类型**:`StatementHandler`
- **作用**:拦截 SQL 语句的执行,处理 SQL 的创建和执行。
- **拦截时机**:
- 在创建 `PreparedStatement` 或 `Statement` 之前和之后。
- 在设置参数之前和之后。
- 在执行 SQL 之前和之后。
```java
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
```
5. **Environment 拦截器**
- **类型**:`Environment`
- **作用**:拦截环境配置,用于配置事务管理器和数据源。
- **拦截时机**:
- 在创建环境配置之前和之后。
```java
@Signature(type = Environment.class, method = "newTransaction", args = {DataSource.class, TransactionIsolationLevel.class, Boolean.class})
```
6. **TransactionFactory 拦截器**
- **类型**:`TransactionFactory`
- **作用**:拦截事务工厂,用于创建事务对象。
- **拦截时机**:
- 在创建事务之前和之后。
```java
@Signature(type = TransactionFactory.class, method = "openConnection", args = {Properties.class})
```
7. **TypeHandlerRegistry 拦截器**
- **类型**:`TypeHandlerRegistry`
- **作用**:拦截类型处理器注册表,用于注册和管理类型处理器。
- **拦截时机**:
- 在注册类型处理器之前和之后。
```java
@Signature(type = TypeHandlerRegistry.class, method = "getTypeHandler", args = {Class.class, JdbcType.class})