我们通过下载并查看PageHelper-Spring-Boot-Starter
源码,了解到PageHelper
是通过实现MyBatis
拦截器接口org.apache.ibatis.plugin.Interceptor
的PageInterceptor类从而实现对SQL重写的。
那么问题来了XDM, PageInterceptor又是具体如何重写SQL的呢?
我们知道拦截器一般是通过反射机制实现的。(本文暂不展开说)。
拦截器中最重要的方法一般命名为intercept
,形式一般是下面代码的样子:
@Override public Object intercept(Invocation invocation) { // 逻辑代码前 invocation.invoke(); // 执行所拦截的实际代码 // 逻辑代码后 }
这里的invocation.invoke()
实际就是相当于我们去执行接口代码或者是执行SQL,如此我们便可以全局的对程序接口或者SQL做某些修改。
我们来看一下PageInterceptor
的拦截器代码:
@Override public Object intercept(Invocation invocation) throws Throwable { try { Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; Object parameter = args[1]; RowBounds rowBounds = (RowBounds) args[2]; ResultHandler resultHandler = (ResultHandler) args[3]; Executor executor = (Executor) invocation.getTarget(); CacheKey cacheKey; BoundSql boundSql; //由于逻辑关系,只会进入一次 if (args.length == 4) { //4 个参数时 boundSql = ms.getBoundSql(parameter); cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql); } else { //6 个参数时 cacheKey = (CacheKey) args[4]; boundSql = (BoundSql) args[5]; } checkDialectExists(); //对 boundSql 的拦截处理 if (dialect instanceof BoundSqlInterceptor.Chain) { boundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.ORIGINAL, boundSql, cacheKey); } List resultList; //调用方法判断是否需要进行分页,如果不需要,直接返回结果 if (!dialect.skip(ms, parameter, rowBounds)) { //判断是否需要进行 count 查询 if (dialect.beforeCount(ms, parameter, rowBounds)) { //查询总数 Long count = count(executor, ms, parameter, rowBounds, null, boundSql); //处理查询总数,返回 true 时继续分页查询,false 时直接返回 if (!dialect.afterCount(count, parameter, rowBounds)) { //当查询总数为 0 时,直接返回空的结果 return dialect.afterPage(new ArrayList(), parameter, rowBounds); } } resultList = ExecutorUtil.pageQuery(dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey); } else { //rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页 resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql); } return dialect.afterPage(resultList, parameter, rowBounds); } finally { if(dialect != null){ dialect.afterAll(); } } }
代码有点多,我们来逐行分析:
前4句,获取拦截中的参数:
Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; Object parameter = args[1]; RowBounds rowBounds = (RowBounds) args[2]; ResultHandler resultHandler = (ResultHandler) args[3];
类型依次为 MappedStatement
,Object
,RowBounds
,ResultHandler
,为了更清楚,我们简单列出其结构:大致可以根据其参数了解这个参数大概是什么类型
- MappedStatement (resource, configuration, id, fetchSize, timeout, statementType, resultSetType, sqlSource, cache, parameterMap, resultMaps, ..., sqlCommandType, keyGenerator,keyProperties, keyColumns, databaseId, statementLog, LanguageDriver, resultSets);
- Object, 不做叙述;
- RowBounds: offset, limit, (类中还有常量NO_ROW_OFFSET=0, NO_ROW_LIMIT = 2147483647);
- ResultHandler<T>:只有一个抽象方法:
public abstract void handleResult(org.apache.ibatis.session.ResultContext<? extends T> arg0);
又是从哪里拦截到的这几个参数呢?或者说,我们拦截的到底是个什么东东?
PageHelper的wiki文档给出了答案:org.apache.ibatis.executor.Executor
.
wiki中说:
在 MyBatis 的拦截器的文档部分,我们知道 Executor 中的 query 方法可以被拦截
其query方法形式为:
<E> List<E> query( MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
看,是不是对应上了4个参数!
话说谁发明的springboot这种接口与实现模式,源码看的类似了,一顿翻找还是漏掉不少源码。