科普文:MyBatis系列之【Mybatis常用的4种拦截器】

240 篇文章 1 订阅
193 篇文章 1 订阅

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

  1. 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:

  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:清除本地缓存。

 Executor(执行器拦截器):

- 用途:拦截MyBatis执行器方法的执行。

- 使用:允许拦截和自定义MyBatis执行器的行为。例如,可以添加缓存、日志记录或审计功能到执行器中。这些拦截器可以在MyBatis执行的不同阶段扩展或修改其行为。您可以通过实现MyBatis提供的相应接口并在MyBatis配置文件中进行配置来实现这些拦截器。

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 语句以及该语句中所需的参数信息。

 StatementHandler(语句拦截器):

-- 用途:拦截SQL语句的执行。

- 使用:可以在SQL语句执行之前修改或增强它们。例如,可以向WHERE子句添加额外的条件或记录执行的语句。分页等

ParameterHandler:

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

ParameterHandler(参数拦截器):

- 用途:拦截SQL语句的参数设置。

- 使用:允许在将参数设置到SQL语句之前修改或验证它们。例如,可以对作为参数传递的敏感信息进行加密或解密。

ResultSetHandler:

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

ResultHandler(结果集拦截器):

- 用途:拦截从SQL语句返回的结果集的处理。

- 使用:可以在将结果集返回给应用程序之前修改或分析它们。例如,可以对结果集数据进行转换或执行额外的计算。

示例

步骤

默认已引入 Mybatis 相关依赖。

  1. 创建一个实现了 MyBatis 提供的 Interceptor 接口的类,这个接口包含一个方法 intercept(Invocation invocation) 
  2. 在 intercept 方法里,通过 invocation 对象可以获取执行的目标方法,你可以在执行目标方法之前或之后加入自己的业务逻辑代码。
  3. 使用 @Intercepts 和 @Signature 注解来配置拦截器,指明想要拦截的接口和方法;关于 @Signature 注解下文详述。
  4. 注册拦截器,下文详述。
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 时,一般有两种方式注册拦截器:

  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.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})

  • 20
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-无-为-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值