PageHelper分页原理(源码)
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分页原理(源码)相关推荐
- concurrenthashmap实现原理_Mybatis:PageHelper分页插件源码及原理剖析
PageHelper是一款好用的开源免费的Mybatis第三方物理分页插件,其实我并不想加上好用两个字,但是为了表扬插件作者开源免费的崇高精神,我毫不犹豫的加上了好用一词作为赞美. 原本以为分页插件, ...
- PageHelper分页插件源码及原理剖析
摘要: com.github.pagehelper.PageHelper是一款好用的开源免费的Mybatis第三方物理分页插件. PageHelper是一款好用的开源免费的Mybatis第三方物理分页 ...
- oracle 分页_Mybatis:PageHelper分页插件源码及原理剖析
点击上方"Java知音",选择"置顶公众号" 技术文章第一时间送达! 作者:祖大俊 my.oschina.net/zudajun/blog/745232 Pag ...
- k线顶分型 python_顶底分型-(K线分类及顶底分型的一种数学原理 源码 贴图)...
好股票软件下载网(www.goodgupiao.com)提示:您正在下载的是:顶底分型-(K线分类及顶底分型的一种数学原理 源码 贴图) 参考缠论,研究了很多天终于将顶底分型进行了具体的数学量化,涵盖 ...
- Unity Fog 原理 源码分析 案例
Unity Fog 原理 源码分析 案例 效果图 简述 背景知识 clip空间坐标的范围 d3d (near,0), unity remapping to (0,far) 越靠近相机z值越小 open ...
- 崔希凡JavaWeb视频教程_day20jdbc分页-成品源码资料
效果如上图,mysql数据库中文存入乱码问题,不知道怎么解决. 跟着视频写了一遍,已经实现增删改查,包括高级搜索就是多条件查询,分页. 源码:https://yunpan.cn/Oc6RE64SnLh ...
- Delaunay 三角剖分3D(原理 + 源码)
文章目录 Delaunay 三角剖分 二维与三维的区别 代码实现< Python版本 > 代码实现< Matlab 版本 > Delaunay 三角剖分 原理部分请参照我的上一 ...
- 仿猎豹垃圾清理 实现原理+源码
仿猎豹垃圾清理(实现原理+源码) 转载请注明出处: 仿猎豹垃圾清理(实现原理+源码) 前几天无意打开猎豹内存大师, 发现它的垃圾清理很强大, 效果也不错, 闲着就研究了下. 不过.. 结果貌似和我想象 ...
- 仿猎豹垃圾清理(实现原理+源码)
仿猎豹垃圾清理(实现原理+源码) 转载请注明出处: 仿猎豹垃圾清理(实现原理+源码) 前几天无意打开猎豹内存大师, 发现它的垃圾清理很强大, 效果也不错, 闲着就研究了下. 不过.. 结果貌似和我想象 ...
最新文章
- chrome java插件_selenium启动Chrome配置参数问题
- 入门4:PHP 语法基础1
- Spring Security基于角色的权限管理
- 分布式文件系统HDFS,大数据存储实战(一)
- 基于TensorFlow Lite的人声识别在端上的实现
- java输出 4 7什么意思_Java学习4_一些基础4_输入输出_16.5.7
- STM32工作笔记0093---DAC数模转换实验-M3
- JAVA Swing GUI设计 WindowBuilder Pro Container使用大全8——JInternalFrame使用
- Vue音乐项目笔记(三)
- Flutter进阶第9篇:检测网络连接,监听网络变化
- 常见数据分析误区:不要让数据误导你!
- 网页制作基础及HTML的笔记,《网页设计与制作项目教程》:网页制作基础知识笔记一...
- android 盒子dns设置,电视盒修改DNS,让你的上网速度更快!
- ssm酒店预订系统ssm酒店管理系统民宿预订ssm酒店客房预订系统SSM客房预订系统
- 数据管理平台与数据治理平台的区别和联系
- 哈达玛矩阵 matlab,哈达玛变换矩阵.ppt
- ubuntu 18.04 开启rc.local
- python 淘宝滑块验证_pyppeteer硬钢掉淘宝登入的滑块验证
- 数字化推动后市场产业变革,开启汽车后市场新篇章
- linux磁盘显示64Z,使用率100%