关于Mybatis分页的实现方式,请移步Mybatis之分页查询。
RowBounds原理
- 当查询执行完成后,对结果集
ResultSet进行处理时,如果此时使用了RowBounds分页,那么就会在结果集中,将游标移动到分页参数指向的起始位置。 - 当结果集
ResultSet的游标被移动后,在while循环中处理时,就会直接从游标指向的分页起始位置开始处理,被跳过的数据不在处理范围内。 while循环执行时,也会判断是否需要处理更多数据,此条件也是根据RowBounds参数中limit参数来判断的。
DefaultResultSetHandler
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
// 当前要处理的结果集
ResultSet resultSet = rsw.getResultSet();
// 根据RowBounds翻页配置,跳过指定的行
skipRows(resultSet, rowBounds);
// 持续处理下一条结果,判断条件为;还有结果需要处理 && 结果集没有关闭 && 还有下一条结果
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
// 经过鉴别器鉴别,确定经过鉴别器分析的最终要使用resultMap
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
// 拿到一行记录,并且将其转化为一个对象
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
// 把这一行记录转化出的对象存起来
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
// 进入该分支表示结果的游标不是只能单步前进
if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
// 直接让游标移动到起始位置
rs.absolute(rowBounds.getOffset());
}
} else {
// 进入到该分支表示结果的游标只能单步前进
for (int i = 0; i < rowBounds.getOffset(); i++) {
if (!rs.next()) {
break;
}
}
}
}
private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) {
return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
}
PageHelper原理
-
PageHelper则是利用Mybatis的插件原理,对Executor接口的query方法进行了拦截。@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}), } ) public class PageInterceptor implements Interceptor { ... } -
在创建
Executor时,如果有插件对Executor进行拦截,则会对Executor对象生成代理,在执行相对应的方法时进行增强处理。public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; // 根据数据库操作类型创建市级执行器 if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } // 根据配置文件中的 settings 节点cacheEnabled配置项确定是否启用缓存 if (cacheEnabled) { // 如果配置启用该缓存 // 使用CachingExecutor装饰实际的执行器 executor = new CachingExecutor(executor); } // 为执行器增加拦截器(插件),以启用各个拦截器的功能 executor = (Executor) interceptorChain.pluginAll(executor); return executor; } -
当程序走到
DefaultSqlSession中时,会调用Executor的query方法,此时就会触发PageHelper的代理,进入PageHelper的逻辑public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { /** * 获取查询语句 * 设置MappedStatement映射,见{@link XMLStatementBuilder#parseStatementNode()} */ MappedStatement ms = configuration.getMappedStatement(statement); /** * 交由执行器进行查询,由于全局配置cacheEnabled默认是打开的,因此此处的executor通常都是CachingExecutor * 获取executor,见{@link Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)} */ return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } -
PageHelper拦截器PageInterceptor执行流程@Override public Object intercept(Invocation invocation) throws Throwable { try { ... //调用方法判断是否需要进行分页,如果不需要,直接返回结果 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); } // 将查询结果设置到Page对象中 return dialect.afterPage(resultList, parameter, rowBounds); } finally { if(dialect != null){ dialect.afterAll(); } } }graph TD A{"分页?"} -->|是| B{"count查询?"} B --是--> C{"count结果?"} B --否--> D["Executor执行分页查询"] C --大于0--> D A --否--> F["Executor执行普通查询"] C --小于等于0--> E["返回空结果"] F --> G["将查询结果设置到Page对象中并返回"] D --> G
以上便是Mybatis中各种分页方式实现的原理。