🍰 个人主页:不摆烂的小劉
🍞文章有不合理的地方请各位大佬指正。
🍉文章不定期持续更新,如果我的文章对你有帮助➡️ 关注🙏🏻 点赞👍 收藏⭐️
前段时间,出了一个线上Bug——Pagehelper分页查询失效。仔细研究了一下Pagehelper。
直接上代码,下面是常见的分页应用代码。
PageMethod.startPage(dto.getPageNum(), dto.getPageSize());
List<ProductVO> products = productDAO.listProducts(dto);
return new PageInfo<>(products);
上面三行代码做了什么事?怎么做的?
文章目录
一、前置知识
1.Sql中的limit
limit 10,20
第11 行(因为索引从 0 开始,偏移量 10 就对应第 11 行)开始选取 20 行数据
limit 10
选取 前10 行数据
二、分页原理
1.PageMethod.startPage(dto.getPageNum(), dto.getPageSize());
- 设置当前要查询的页码(
dto.getPageNum()
)和每页显示的记录数量(dto.getPageSize()
)
- 将分页参数存放到
ThreadLocal
中: ThreadLocal
在线程内直接获取资源,避免方法调用链中频繁地传递分页参数
2.List<ProductVO> products = productDAO.listProducts(dto);
2.1MyBatis拦截器
- 执行数据库查询,自动应用分页逻辑
Pagehelper
底层通过拦截器实现分页.参考MyBatis
的拦截器的文档部分,Executor
中的query
方法可以被拦截,被拦截有两个方法
<E> List<E> query(
MappedStatement ms,
Object parameter,
RowBounds rowBounds,
ResultHandler resultHandler,
CacheKey cacheKey,
BoundSql boundSql) throws SQLException;
<E> List<E> query(
MappedStatement ms,
Object parameter,
RowBounds rowBounds,
ResultHandler resultHandler) throws SQLException;
6参数query
查询, 4参数query
查询
Mybatis
通过org.apache.ibatis.session.Configuration
加入拦截,MyBatis
会按照拦截器配置的顺序依次添加到 interceptorChain
中
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
2.2PageHelper
拦截器PageInterceptor
- 注解
@Intercepts
:定义了一个拦截器,用于拦截MyBatis
的Executor
类中的query
方法。 - 注解
@Signature
:指定了要拦截的方法签名,包括方法类型、方法名和参数类型。 - 可以看出这里拦截了
Executor
中6参数query
查询, 4参数query
查询 PageInterceptor
中的intercept
方法是整个分页查询的关键,核心代码如下:
流程图
围绕这块核心代码分析一下:
- 判断是否需要进行 count 查询
dialect.beforeCount(ms, parameter, rowBounds)
在设置分页参数时,两种构造器,设置count
是否开启查询总数
//默认开启 this.count = true
PageMethod.startPage(dto.getPageNum(), dto.getPageSize());
// 关闭查询总数
PageMethod.startPage(dto.getPageNum(), dto.getPageSize(),false);
-
查询总数
Long count = count(executor, ms, parameter, rowBounds, null, boundSql);
执行自动创建的 count 查询
获取查询总数的Sql
将查询的Sql
包起来,拼装count(Sql)
,查询总数。 -
执行查询
resultList = ExecutorUtil.pageQuery(dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
- Sql如何拼接limit?,?
获取limit
,拼接到查询Sql
尾部
- 分页查询后,处理分页结果集
return dialect.afterPage(resultList, parameter, rowBounds);
将查询到的数据集合集存放到Page集合中
Page属于List集合
Page类定义
public class Page extends ArrayList implements Closeable
3.new PageInfo<>(products);
先在PageInfo
父类中执行supep(this)
计算数据集的总数,再设置分页参数
在PageInfo
父类中计算总数和设置分页参数都会判断是否属于Page
。(易错)
以上是在底层代码层面解析分页插件Pagehelper的原理.
三、思考
1.为什么将查询数据存放到Page中?
查询结果(即数据列表),还封装了与分页相关的其他信息,比如当前页码、总记录数、每页显示的记录数等
2.是否开启总数查询?
- 未开启查询总数
PageMethod.startPage(dto.getPageNum(), dto.getPageSize(),false);
- 开启查询总数
PageMethod.startPage(dto.getPageNum(), dto.getPageSize());
count(*)与数据量关系
InnoDB
存储引擎(以 MySQL
为例)
数据量越多查询速度越慢,原因需要读取大量的数据页,涉及更多的磁盘 I/O 操作和 CPU 计算所以比较耗时
31条数据查询0.1s
500w条数据查询约6.4s
所以是否开启分页,要考虑数据量的影响。
参考:
https://pagehelper.github.io/docs/
分页插件PageHelper工作原理_pagehelper底层原理
使用PageHelper实现分页查询
PageHelper原理深度剖析(集成+源码)
彻底搞懂MyBatis插件原理及PageHelper原理
分页查询-PageHelper底层原理分析以及使用PageHelper的步骤
看到这里的友友们 点个赞吧👍👍👍
🍉文章不定期持续更新