likes
comments
collection
share

Mybatis拦截器(Interceptor)的理解与实践

作者站长头像
站长
· 阅读数 3

MyBatis 的拦截器是一个十分强大的特性,它可以让我们在 MyBatis 调用数据库操作的过程中插入自己的逻辑,非常适合做一些数据操作的审计、性能优化、事务管理、执行日志输出等。

拦截器的触发策略

拦截接口

MyBatis 允许拦截 SQL 生命周期中的四个关键节点:ExecutorParameterHandlerResultSetHandlerStatementHandler ,它们在数据库操作中扮演核心角色,Mybatis 提供了在这四个对象执行前后插入自定义逻辑的强大支持。通常情况下,如果定义的所有接口的拦截器,拦截顺序大致如下:

  1. StatementHandler
    • 在 MyBatis 准备执行 SQL 之前,首先会创建 Statement 对象,这时会触发对 StatementHandler 的拦截。
    • 使用 StatementHandler 拦截器可以在 SQL 语句被发送到数据库执行前进行自定义操作,比如修改原始 SQL 语句、设置特殊的 Statement 属性等。
  1. ParameterHandler
    • 在 Statement 准备执行之前,ParameterHandler 将会被调用,以设置SQL语句中的参数。
    • 通过拦截 ParameterHandler,可以在 SQL 参数绑定前后进行操作。适用于复杂的参数处理逻辑,比如加密/解密数据,或者对特殊的参数格式进行处理。
  1. Executor
    • 执行器 Executor 是整个执行过程的中心,它会调用上述的 StatementHandler 和 ParameterHandler 来准备命令并执行。
    • 拦截 Executor ,可以在 SQL 执行前后添加逻辑,比如缓存的逻辑,在查询语句执行前后检查和添加缓存。
  1. ResultSetHandler
    • SQL 语句执行后,如果有结果集返回,MyBatis 将使用 ResultSetHandler 来处理这些结果集,将 JDBC 返回的 ResultSet 转化为 MyBatis 中指定的结果对象。
    • 拦截 ResultSetHandler 支持在结果集映射过程中插入自定义逻辑,比如结果集的加工处理、性能统计等。

如果定义了所有这些拦截器,它们将会按照上面的顺序被触发。但是,拦截器的触发会根据具体的执行操作来调整。例如,如果SQL执行不涉及结果集的处理(如插入、更新或删除操作),ResultSetHandler将不会被触发。同样,如果在Executor拦截器中终止了SQL执行,随后的拦截器也不会再被触发。

拦截方法

上述四个核心接口提供了多个精细化方法,允许在数据库操作的不同阶段进行精确的干预和拦截。

Executor:

  1. update:负责执行 insert、update、delete 三种类型的 SQL 语句。
  2. query:负责执行 select 类型的 SQL 语句。
  3. queryCursor:负责执行 select 类型的 SQL 语句,返回 Cursor 对象。
  4. flushStatements:提交批处理语句,返回批处理结果。
  5. commit:提交事务。
  6. rollback:回滚事务。
  7. getTransaction:获取事务对象。
  8. close:关闭 executor,同时根据参数决定是否强制回滚未提交的事务。
  9. isClosed:检查 executor 是否已经关闭。
  10. clearLocalCache:清除本地缓存。

StatementHandler:

  1. prepare:准备一个数据库 Statement 对象以待执行。这个方法根据配置和上下文信息来创建一个 PreparedStatement 或 CallableStatement 对象。
  2. parameterize:在 SQL 语句被执行之前,该方法负责将 SQL 参数设置到 PreparedStatement 对象中。
  3. batch:负责处理批量执行的逻辑,将多个更新语句作为一个批处理提交。
  4. update:执行写操作(insert、update、delete)的 SQL 语句。
  5. query:执行查询操作(select)的 SQL 语句,并返回结果。
  6. queryCursor:负责执行查询操作(select)SQL 语句,返回 Cursor 对象。
  7. getBoundSql:返回 BoundSql 对象,这个对象包含了要执行的 SQL 语句以及该语句中所需的参数信息。

ParameterHandler:

  1. getParameterObject:此方法用于获取 SQL 参数对象。
  2. setParameters:此方法将 SQL 命令中的参数与实际的参数对象相匹配。它负责将传入的参数设置到 PreparedStatement 中。

ResultSetHandler:

  1. handleResultSets:这是主要的方法之一,它接受一个 Statement 对象作为参数,并将 SQL执行的结果 ResultSet 映射到结果对象。
  2. handleOutputParameters:当存储过程调用完成之后,这个方法会处理其输出参数。它同样接受一个 Statement 对象作为参数。

简单的拦截器demo

步骤

默认已引入 Mybatis 相关依赖。

  1. 创建一个实现了 MyBatis 提供的 Interceptor 接口的类,这个接口包含一个方法 intercept(Invocation invocation)
  1. intercept 方法里,通过 invocation 对象可以获取执行的目标方法,你可以在执行目标方法之前或之后加入自己的业务逻辑代码。
  2. 使用 @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;
    }
    
}
  1. 注册拦截器,下文详述。

注册 Mybatis 拦截器

在 Spring 框架中,如果创建了一个拦截器类但没有将其注册为 Spring Bean,那么这个拦截器不会自动被 MyBatis 检测到和使用,导致拦截器失效。为了让拦截器生效,需要在配置中明确声明并注册这个拦截器。

Spring

在使用 Spring 配置 MyBatis 时,一般有两种方式注册拦截器:

  1. 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>
  1. 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 的配置过程。同样有两种方式注册拦截器:

  1. 在 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());
            }
        };
    }
}
  1. 在 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.classParameterHandler.classResultSetHandler.classStatementHandler.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 {
    //...
}