mybatis 插件

黄光跃 / 2023-07-21 / 原文

PageHelper

先看 PageHelper 的插件签名

@Intercepts(
        {
                @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}),
        }
)
  1. @Intercepts 表示这是一个 mybatis 的插件,可以配置多个 @Signature
  2. @Signature 包含三个属性 type、method、args
    1. type 表示要拦截的对象。4 个选项:Executor、ParameterHandler、ResultSetHandler 以及 StatementHandler
    2. method 表示要拦截的方法。对应拦截对象的方法
    3. args 表示拦截方法的参数。对应拦截对象的方法参数

PageHelper 的做法就是拦截 Executor 的 两个 query 方法

type 拦截对象

Executor

  • update:该方法会在所有的 INSERT、 UPDATE、 DELETE 执行时被调用,如果想要拦截这些操作,可以通过该方法实现。
  • query:该方法会在 SELECT 查询方法执行时被调用,方法参数携带了很多有用的信息,如果需要获取,可以通过该方法实现。
  • queryCursor:当 SELECT 的返回类型是 Cursor 时,该方法会被调用。
  • flushStatements:当 SqlSession 方法调用 flushStatements 方法或执行的接口方法中带有 @Flush 注解时该方法会被触发。
  • commit:当 SqlSession 方法调用 commit 方法时该方法会被触发。
  • rollback:当 SqlSession 方法调用 rollback 方法时该方法会被触发。
  • getTransaction:当 SqlSession 方法获取数据库连接时该方法会被触发。
  • close:该方法在懒加载获取新的 Executor 后会被触发。
  • isClosed:该方法在懒加载执行查询前会被触发。

ParameterHandler

  • getParameterObject:在执行存储过程处理出参的时候该方法会被触发。
  • setParameters:设置 SQL 参数时该方法会被触发。

ResultSetHandler

  • handleResultSets:该方法会在所有的查询方法中被触发(除去返回值类型为 Cursor 的查询方法),一般来说,如果我们想对查询结果进行二次处理,可以通过拦截该方法实现。
  • handleCursorResultSets:当查询方法的返回值类型为 Cursor 时,该方法会被触发。
  • handleOutputParameters:使用存储过程处理出参的时候该方法会被调用。

StatementHandler

  • prepare:该方法在数据库执行前被触发。
  • parameterize:该方法在 prepare 方法之后执行,用来处理参数信息。
  • batch:如果 MyBatis 的全剧配置中配置了 defaultExecutorType="BATCH",执行数据操作时该方法会被调用。
  • update:更新操作时该方法会被触发。
  • query:该方法在 SELECT 方法执行时会被触发。
  • queryCursor:该方法在 SELECT 方法执行时,并且返回值为 Cursor 时会被触发。

运行原理

没有插件的运行图

有插件的运行图

图片Base64编码

自定义拦截器

自定义一个拦截Executor的query()方法的插件,用于打印SQL语句以及执行时间

@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class QueryPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Object parameter = invocation.getArgs()[1];
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        String sql = boundSql.getSql();
        long startTime = System.currentTimeMillis();
        Object result = invocation.proceed();
        long endTime = System.currentTimeMillis();
        System.out.println("Executed SQL [" + sql + "] in " + (endTime - startTime) + "ms");
        return result;
    }
 
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
 
    @Override
    public void setProperties(Properties properties) {
    }
}

拦截器注册

xml 方式

<plugins>
    <plugin interceptor="com.xiaolyuh.mybatis.EditPlugin">
        <property name="args1" value="参数示例"/>
    </plugin>
</plugins>

springboot 方式

// 注册插件方式1
@Configuration
public class QueryPluginConfig {
    @Bean
    public QueryPlugin queryPlugin() {
        return new QueryPlugin();
    }
}
// 注册插件方式2
@Configuration
public class QueryPluginConfig {
    
//    @Bean
//    public ConfigurationCustomizer configurationCustomizer() {
//        return new ConfigurationCustomizer() {
//            @Override
//            public void customize(org.apache.ibatis.session.Configuration configuration) {
//                //插件拦截链采用了责任链模式,执行顺序和加入连接链的顺序有关
//                QueryPlugin queryPlugin = new QueryPlugin();
//                configuration.addInterceptor(queryPlugin);
//            }
//        };
//    }


    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> {
            // 插件拦截链采用了责任链模式,执行顺序和加入连接链的顺序有关
            QueryPlugin queryPlugin = new QueryPlugin();
            configuration.addInterceptor(queryPlugin);
        };
    }
}