PageHelper分页原理

PageHelper是我们经常使用的一个分页插件,之前咱们一直处于使用阶段的,今天咱们去探究一下其中的原理。
SQL语句实现分页查询知识,就不在赘述了。

LIMIT i,a;i:是指查询的索引值(默认是0)a:是指查询的数量值SELECT id FROM '表' WHERE '条件' LIMIT (i-1)*a,a;

首先,咱们使用PageHelper分页首先要在查询数据库数据方法前先调用PageHelper.startPage方法。

pageNum 当前页数
pageSize  每页有多少条数据
orderBy    数据的排序条件
PageHelper.startPage(pageNum, pageSize, orderBy);

该方法的三个参数都是前端传递过来的,咱们debug进入这个方法一探究竟,发现这个方法根据,pageNum、pageSize创建了一个page类,然后给他set了一个排序的条件,至此创建出一个page分页类,在这个方法之后调用的查询数据库方法都会进行分页查询。

public static <E> Page<E> startPage(int pageNum, int pageSize, String orderBy) {Page<E> page = startPage(pageNum, pageSize);page.setOrderBy(orderBy);return page;
}

咱们继续来看为啥会实现分页的,直接debug进入startPage方法下的查询数据库的方法。进入了一个cglib代理的分页拦截器,咱们这个不需要aop代理之类的操作直接调用方法就可以。

@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {Object oldProxy = null;boolean setProxyContext = false;Object target = null;TargetSource targetSource = this.advised.getTargetSource();Object var16;try {if (this.advised.exposeProxy) {oldProxy = AopContext.setCurrentProxy(proxy);setProxyContext = true;}target = targetSource.getTarget();Class<?> targetClass = target != null ? target.getClass() : null;List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);Object retVal;if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);retVal = methodProxy.invoke(target, argsToUse);} else {retVal = (new CglibAopProxy.CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)).proceed();}retVal = CglibAopProxy.processReturnType(proxy, target, method, retVal);var16 = retVal;} finally {if (target != null && !targetSource.isStatic()) {targetSource.releaseTarget(target);}if (setProxyContext) {AopContext.setCurrentProxy(oldProxy);}}return var16;
}

直接执行这个方法 retVal = methodProxy.invoke(target, argsToUse);调用原方法,现在还和分页原理无关,请看官耐心等待

public Object invoke(Object obj, Object[] args) throws Throwable {try {this.init();MethodProxy.FastClassInfo fci = this.fastClassInfo;return fci.f1.invoke(fci.i1, obj, args);} catch (InvocationTargetException var4) {throw var4.getTargetException();} catch (IllegalArgumentException var5) {if (this.fastClassInfo.i1 < 0) {throw new IllegalArgumentException("Protected method: " + this.sig1);} else {throw var5;}}
}

return fci.f1.invoke(fci.i1, obj, args);执行原方法

/*** 根据条件分页查询字典类型* * @param dictType 字典类型信息* @return 字典类型集合信息*/
@Override
public List<SysDictType> selectDictTypeList(SysDictType dictType)
{return dictTypeMapper.selectDictTypeList(dictType);
}

来到这个方法,method.getDeclaringClass()判断这个类是否是object基础类型的类是的话直接调基础类型,不是的话调用this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession)

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);} catch (Throwable var5) {throw ExceptionUtil.unwrapThrowable(var5);}
}

来到这个方法sqlSession和 args继续调用 ,sqlSession是操作数据库的类

public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {return this.mapperMethod.execute(sqlSession, args);
}

进入这个方法,因为是 SELECT 所以执行result = this.executeForMany(sqlSession, args);

public Object execute(SqlSession sqlSession, Object[] args) {Object result;Object param;switch(this.command.getType()) {case INSERT:param = this.method.convertArgsToSqlCommandParam(args);result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));break;case UPDATE:param = this.method.convertArgsToSqlCommandParam(args);result = this.rowCountResult(sqlSession.update(this.command.getName(), param));break;case DELETE:param = this.method.convertArgsToSqlCommandParam(args);result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));break;case SELECT:if (this.method.returnsVoid() && this.method.hasResultHandler()) {this.executeWithResultHandler(sqlSession, args);result = null;} else if (this.method.returnsMany()) {result = this.executeForMany(sqlSession, args);} else if (this.method.returnsMap()) {result = this.executeForMap(sqlSession, args);} else if (this.method.returnsCursor()) {result = this.executeForCursor(sqlSession, args);} else {param = this.method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(this.command.getName(), param);if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);}}break;case FLUSH:result = sqlSession.flushStatements();break;default:throw new BindingException("Unknown execution method for: " + this.command.getName());}if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");} else {return result;}
}

来到executeForMany方法

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {Object param = this.method.convertArgsToSqlCommandParam(args);List result;if (this.method.hasRowBounds()) {RowBounds rowBounds = this.method.extractRowBounds(args);result = sqlSession.selectList(this.command.getName(), param, rowBounds);} else {result = sqlSession.selectList(this.command.getName(), param);}if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);} else {return result;}
}

执行result = sqlSession.selectList(this.command.getName(), param);

public <E> List<E> selectList(String statement, Object parameter) {return this.sqlSessionProxy.selectList(statement, parameter);
}

又invoke晕了,可能因为是selectList方法是代理出来的

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);Object unwrapped;try {Object result = method.invoke(sqlSession, args);if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {sqlSession.commit(true);}unwrapped = result;} catch (Throwable var11) {unwrapped = ExceptionUtil.unwrapThrowable(var11);if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);sqlSession = null;Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);if (translated != null) {unwrapped = translated;}}throw (Throwable)unwrapped;} finally {if (sqlSession != null) {SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);}}return unwrapped;
}

Object result = method.invoke(sqlSession, args);调用原方法DefaultSqlSession 中selectList方法

public <E> List<E> selectList(String statement, Object parameter) {return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

执行this.selectList(statement, parameter, RowBounds.DEFAULT);

private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {List var6;try {MappedStatement ms = this.configuration.getMappedStatement(statement);var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);} catch (Exception var10) {throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var10, var10);} finally {ErrorContext.instance().reset();}return var6;
}

这个方法貌似没作用

private Object wrapCollection(Object object) {return ParamNameResolver.wrapToMapIfCollection(object, (String)null);
}

调用var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler); 如果然代理的方法都会进入invoke ,这个类是个InvocationHandler

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);} catch (Exception var5) {throw ExceptionUtil.unwrapThrowable(var5);}
}

绕来绕去,来到关键点了激动 pageintercepted类中的拦截器!!!

public Object intercept(Invocation invocation) throws Throwable {Object var16;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) {boundSql = ms.getBoundSql(parameter);cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);} else {cacheKey = (CacheKey)args[4];boundSql = (BoundSql)args[5];}this.checkDialectExists();if (this.dialect instanceof Chain) {boundSql = ((Chain)this.dialect).doBoundSql(Type.ORIGINAL, boundSql, cacheKey);}List resultList;if (!this.dialect.skip(ms, parameter, rowBounds)) {if (this.dialect.beforeCount(ms, parameter, rowBounds)) {Long count = this.count(executor, ms, parameter, rowBounds, (ResultHandler)null, boundSql);if (!this.dialect.afterCount(count, parameter, rowBounds)) {Object var12 = this.dialect.afterPage(new ArrayList(), parameter, rowBounds);return var12;}}resultList = ExecutorUtil.pageQuery(this.dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);} else {resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);}var16 = this.dialect.afterPage(resultList, parameter, rowBounds);} finally {if (this.dialect != null) {this.dialect.afterAll();}}return var16;
}

resultList = ExecutorUtil.pageQuery(this.dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);获得最后结果数据

public static <E> List<E> pageQuery(Dialect dialect, Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql, CacheKey cacheKey) throws SQLException {if (!dialect.beforePage(ms, parameter, rowBounds)) {return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);} else {parameter = dialect.processParameterObject(ms, parameter, boundSql, cacheKey);String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, cacheKey);BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);Iterator var12 = additionalParameters.keySet().iterator();while(var12.hasNext()) {String key = (String)var12.next();pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));}if (dialect instanceof Chain) {pageBoundSql = ((Chain)dialect).doBoundSql(Type.PAGE_SQL, pageBoundSql, cacheKey);}return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, pageBoundSql);}
}

执行String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, cacheKey);

public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {return this.autoDialect.getDelegate().getPageSql(ms, boundSql, parameterObject, rowBounds, pageKey);
}

获取分页的sql语句,Page page = this.getLocalPage();获取存储到本地线程的 page 类

public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {String sql = boundSql.getSql();Page page = this.getLocalPage();String orderBy = page.getOrderBy();if (StringUtil.isNotEmpty(orderBy)) {pageKey.update(orderBy);sql = OrderByParser.converToOrderBySql(sql, orderBy);}return page.isOrderByOnly() ? sql : this.getPageSql(sql, page, pageKey);
}
getLocalPage在本地获取page
public static <T> Page<T> getLocalPage() {return (Page)LOCAL_PAGE.get();
}

进入converToOrderBySql,可以看到給普通的sql语句中拼接了一个" order by "排序的条件,完成了排序的需求

public static String converToOrderBySql(String sql, String orderBy) {Statement stmt = null;try {stmt = CCJSqlParserUtil.parse(sql);Select select = (Select)stmt;SelectBody selectBody = select.getSelectBody();List<OrderByElement> orderByElements = extraOrderBy(selectBody);String defaultOrderBy = PlainSelect.orderByToString(orderByElements);if (defaultOrderBy.indexOf(63) != -1) {throw new PageException("原SQL[" + sql + "]中的order by包含参数,因此不能使用OrderBy插件进行修改!");}sql = select.toString();} catch (Throwable var7) {log.warn("处理排序失败: " + var7 + ",降级为直接拼接 order by 参数");}return sql + " order by " + orderBy;
}

return page.isOrderByOnly() ? sql : this.getPageSql(sql, page, pageKey);判断是否只是orderby排序如果需要分页进入getPageSql方法,拼接 LIMIT语句
1.如果是第一页拼接LIMIT ?
2.不是的话拼接LIMIT ?, ?

   public String getPageSql(String sql, Page page, CacheKey pageKey) {StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);sqlBuilder.append(sql);if (page.getStartRow() == 0L) {sqlBuilder.append("\n LIMIT ? ");} else {sqlBuilder.append("\n LIMIT ?, ? ");}return sqlBuilder.toString();}
}

至此分页的基本功能就完成了拼接完成了一个标准的分页sql语句

SELECT dict_id, dict_name, dict_type, status, create_by, create_time, remark FROM sys_dict_type order by dict_id ascLIMIT ?, ?

分页源码代码的运行步骤

总结:pageHpler原理其实还简单,就是有一个PageInterceptor拦截器中拦截了原生的sql语句,拼接对应的排序orderby条件和limit分页位置

如果没有批评,赞美将毫无意义,欢迎指正批评。

路漫漫其修远兮,吾将上下而求索

PageHelper分页原理(源码)相关推荐

  1. concurrenthashmap实现原理_Mybatis:PageHelper分页插件源码及原理剖析

    PageHelper是一款好用的开源免费的Mybatis第三方物理分页插件,其实我并不想加上好用两个字,但是为了表扬插件作者开源免费的崇高精神,我毫不犹豫的加上了好用一词作为赞美. 原本以为分页插件, ...

  2. PageHelper分页插件源码及原理剖析

    摘要: com.github.pagehelper.PageHelper是一款好用的开源免费的Mybatis第三方物理分页插件. PageHelper是一款好用的开源免费的Mybatis第三方物理分页 ...

  3. oracle 分页_Mybatis:PageHelper分页插件源码及原理剖析

    点击上方"Java知音",选择"置顶公众号" 技术文章第一时间送达! 作者:祖大俊 my.oschina.net/zudajun/blog/745232 Pag ...

  4. k线顶分型 python_顶底分型-(K线分类及顶底分型的一种数学原理 源码 贴图)...

    好股票软件下载网(www.goodgupiao.com)提示:您正在下载的是:顶底分型-(K线分类及顶底分型的一种数学原理 源码 贴图) 参考缠论,研究了很多天终于将顶底分型进行了具体的数学量化,涵盖 ...

  5. Unity Fog 原理 源码分析 案例

    Unity Fog 原理 源码分析 案例 效果图 简述 背景知识 clip空间坐标的范围 d3d (near,0), unity remapping to (0,far) 越靠近相机z值越小 open ...

  6. 崔希凡JavaWeb视频教程_day20jdbc分页-成品源码资料

    效果如上图,mysql数据库中文存入乱码问题,不知道怎么解决. 跟着视频写了一遍,已经实现增删改查,包括高级搜索就是多条件查询,分页. 源码:https://yunpan.cn/Oc6RE64SnLh ...

  7. Delaunay 三角剖分3D(原理 + 源码)

    文章目录 Delaunay 三角剖分 二维与三维的区别 代码实现< Python版本 > 代码实现< Matlab 版本 > Delaunay 三角剖分 原理部分请参照我的上一 ...

  8. 仿猎豹垃圾清理 实现原理+源码

    仿猎豹垃圾清理(实现原理+源码) 转载请注明出处: 仿猎豹垃圾清理(实现原理+源码) 前几天无意打开猎豹内存大师, 发现它的垃圾清理很强大, 效果也不错, 闲着就研究了下. 不过.. 结果貌似和我想象 ...

  9. 仿猎豹垃圾清理(实现原理+源码)

    仿猎豹垃圾清理(实现原理+源码) 转载请注明出处: 仿猎豹垃圾清理(实现原理+源码) 前几天无意打开猎豹内存大师, 发现它的垃圾清理很强大, 效果也不错, 闲着就研究了下. 不过.. 结果貌似和我想象 ...

最新文章

  1. chrome java插件_selenium启动Chrome配置参数问题
  2. 入门4:PHP 语法基础1
  3. Spring Security基于角色的权限管理
  4. 分布式文件系统HDFS,大数据存储实战(一)
  5. 基于TensorFlow Lite的人声识别在端上的实现
  6. java输出 4 7什么意思_Java学习4_一些基础4_输入输出_16.5.7
  7. STM32工作笔记0093---DAC数模转换实验-M3
  8. JAVA Swing GUI设计 WindowBuilder Pro Container使用大全8——JInternalFrame使用
  9. Vue音乐项目笔记(三)
  10. Flutter进阶第9篇:检测网络连接,监听网络变化
  11. 常见数据分析误区:不要让数据误导你!
  12. 网页制作基础及HTML的笔记,《网页设计与制作项目教程》:网页制作基础知识笔记一...
  13. android 盒子dns设置,电视盒修改DNS,让你的上网速度更快!
  14. ssm酒店预订系统ssm酒店管理系统民宿预订ssm酒店客房预订系统SSM客房预订系统
  15. 数据管理平台与数据治理平台的区别和联系
  16. 哈达玛矩阵 matlab,哈达玛变换矩阵.ppt
  17. ubuntu 18.04 开启rc.local
  18. python 淘宝滑块验证_pyppeteer硬钢掉淘宝登入的滑块验证
  19. 数字化推动后市场产业变革,开启汽车后市场新篇章
  20. linux磁盘显示64Z,使用率100%

热门文章

  1. Java打印个人基本信息
  2. DNS Query Types
  3. 【软件测试】Python自动化软件测试算是程序员吗?
  4. D 创始人及员工,被批捕!这和 B站有啥关系?
  5. java毕业设计企业人事信息管理mybatis+源码+调试部署+系统+数据库+lw
  6. 视频教程-基于深度学习的计算机视觉:原理与实践(上部)-计算机视觉
  7. c#实现文件重命名操作
  8. nodejs图片处理工具gm用法
  9. PADS显示元件值并打印贴片图
  10. android elf 加固_Android so加固的简单脱壳