标签:pre access 包装 dstat tar getconf ati tac 页码
通过查询时指定 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) { } }
完成。
标签:pre access 包装 dstat tar getconf ati tac 页码
原文地址:https://www.cnblogs.com/hellohello/p/12218976.html