mybatis插件机制

Posted hellohello

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis插件机制相关的知识,希望对你有一定的参考价值。

默认的分页机制

通过查询时指定 RowBounds 参数,如executor查询数据得出1000条数据,然后使用 DefaultResultSetHandler 处理结果集,内部使用基于内存的分页,即对这1000条数据进行不停地跳过,最终返回特定页码范围的数据

 

RowBounds类包装了两个分页参数:offset和limit

RowBounds.DEFAULT:代表不分页,即offset=0,limit=Integer.MAX_VALUE

插件机制

参考这里

mybatis插件机制提供了mybatis的拓展点。

根据拦截点,指定不同的Signature

@Intercepts({
    @Signature(
        type = Executor.class,
        method = "query",
        args ={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
    )
})
public class ExamplePlugin implements Interceptor {
    // 省略逻辑
}

然后把这个类配置到相关配置文件中。

创建了Executor之后,会执行如下代码对executor(默认是CachingExecutor)进行植入

public Object pluginAll(Object executor) {
    // 遍历拦截器集合
    for (Interceptor interceptor : interceptors) {
        // 调用拦截器的 plugin 方法植入相应的插件逻辑
        executor = interceptor.plugin(executor);
    }
    return executor;
}

每循环一次,都可能生成一个jdk代理,层层嵌套,最里层才是executor。

 

jdk动态代理的invoke方法内会判断当前调用的方法是否配置在插件的 @Signature 注解中,若是,则执行插件逻辑,调用intercept方法。

interceptor.intercept(new Invocation(target, method, args));

Invocation.proceed方法会触发更里层的调用,即触发下一个代理链节点。最终会触发到被代理对象executor的方法。

自定义插件

自定义一个基于mysql,通过RowBounds参数指定的分页功能插件

@Intercepts({
    @Signature(
        type = Executor.class,    // 目标类
        method = "query",    // 目标方法
        args ={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
    )
})
public class MySqlPagingPlugin implements Interceptor {

    private static final Integer MAPPED_STATEMENT_INDEX = 0;
    private static final Integer PARAMETER_INDEX = 1;
    private static final Integer ROW_BOUNDS_INDEX = 2;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    
        // 这里获取到的参数顺序是按照@Signature.args中的次序
        Object[] args = invocation.getArgs();
        
        // 获取到分页参数
        RowBounds rb = (RowBounds) args[ROW_BOUNDS_INDEX];
        
        // 如果当前已经指定了不需要分页,则不执行插件后续的分页逻辑
        if (rb == RowBounds.DEFAULT) {
            return invocation.proceed();
        }
        
        // 将原 RowBounds 参数设为 RowBounds.DEFAULT,关闭 MyBatis 内置的分页机制
        args[ROW_BOUNDS_INDEX] = RowBounds.DEFAULT;

        MappedStatement ms = (MappedStatement) args[MAPPED_STATEMENT_INDEX];
        BoundSql boundSql = ms.getBoundSql(args[PARAMETER_INDEX]);

        // 获取 SQL 语句,拼接 limit 语句
        String sql = boundSql.getSql();
        String limit = String.format("LIMIT %d,%d", rb.getOffset(), rb.getLimit());
        sql = sql + " " + limit;

        // 创建一个 StaticSqlSource,并将拼接好的 sql 传入
        SqlSource sqlSource = new StaticSqlSource(ms.getConfiguration(), sql, boundSql.getParameterMappings());

        // 通过反射获取并设置 MappedStatement 的 sqlSource 字段
        Field field = MappedStatement.class.getDeclaredField("sqlSource");
        field.setAccessible(true);
        field.set(ms, sqlSource);
        
        // 执行被拦截方法
        return invocation.proceed();
    }

    // 这个方法就是以上创建完executor后,调用的方法,这个wrap内会根据 看传入的target是否满足@Signature中指定的条件
    // 满足的返回一个创建并返回一个动态代理,否则返回源对象
    // 因为插件可以作用很多地方,不仅仅在executor上,所以以下函数并不总是返回代理对象
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

完成。

以上是关于mybatis插件机制的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis 源码分析 - 插件机制

MyBatis 源码分析 - 插件机制

MyBatis 源码分析 - 插件机制

mybatis插件机制源码解析

MyBatis自定义插件机制分析(源码级剖析)

SpringBoot + Mybatis系列插件机制 Interceptor