一、前言
大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。
只要是干过后台系统的同学应该都做过分页查询吧,前端发送带有页码(pageNum)和每页显示数量(pageSize)的请求,后端根据这些参数来提取并返回相应的数据集。在SpringBoot框架中,经常会使用Mybatis+PageHelper的方式实现这个功能。
但大家可能对分页合理化
这个词有点儿陌生,不过应该都遇到过因为它产生的问题。这些问题不会触发明显的错误,所以大家一般都忽视了这个问题。那么啥是分页合理化
,我来举几个例子:
1. 请求页码超出范围:
2. 请求页码小于1:
3. 请求的数据大小小于1:
4. 请求的数据大小不合理:
二、为啥要设置分页合理化?
其实上面那些问题对于后端来讲很合理,页码和页大小设置不正确查询不出来值难道不合理吗?唯一的问题就是如果一次性查询太多条数据服务器压力确实大,但如果是产品要求的那也没办法呀!
真正让我不得不解决这个问题的原因是前端的一个BUG,这个BUG是啥样的呢?我来给大家描述一下。
1. BUG复现
我们先看看前端的分页组件
那么,现在我问一个问题:我们切换到第二页,把第二页仅剩的一条数据给删除掉,会出现什么情况?
理想情况:页码自动切换到第1页,并查询第一页的数据;
真实情况:页码切换到了第1页,但是查询不到数据,这明显就是一个BUG!
2. BUG分析
1. 用户切换到第二页,前端发起了请求,如:http://localhost:8080/user/pageQuery?pageNum=2&pageSize=5 ,此时第2页有一条数据;
2. 用户删除第2页的唯一数据后,前端发起查询请求,但还是第2页的查询,因为总数据的变化前端只能通过下一次的查询才能知道,但此时数据查询为空;
3. 虽然第二次查询的数据集为空,但是总条数已经变化了,只剩下5条,前端分页组件根据计算得出只剩下一页,所以自动切换到第1页;
三、设置分页合理化
咋一听这个BUG有点儿复杂,但如果你使用的是PageHelper框架,那么修复它非常简单,只需要两行配置
。
在application.yml或application.properties中添加
pagehelper.helper-dialect=mysql
pagehelper.reasonable=true
只要加了这两行配置,这个BUG就能解决。因为配置是全局的,如果你只想对单个查询场景生效,那就在设置分页参数的时候,加一个参数,如下:
PageHelper.startPage(pageNumber, pageSize, true);
四、分页合理化配置的原理说明
这个BUG如果要自己解决的话,是不是感觉有点头痛了,但是人家PageHelper早就想到这个问题了,就像游戏开挂一样,一个配置就解决了这个麻烦的问题。
用的时候确实很爽,但是我却有点担心,这个配置现在解决了这个BUG,会不会导致新的BUG呢?如果真的出现了新BUG,我应该怎么做呢?所以我决定研究一下它的基础原理。
在com.github.pagehelper.Page类下,找到了这段核心源码,这段应该就是分页合理化的实现逻辑
// 省略其他代码
public Page<E> setReasonable(Boolean reasonable) {
if (reasonable == null) {
return this;
}
this.reasonable = reasonable;
//分页合理化,针对不合理的页码自动处理
if (this.reasonable && this.pageNum <= 0) {
this.pageNum = 1;
calculateStartAndEndRow();
}
return this;
}
// 省略其他代码
// 省略其他代码
/**
* 计算起止行号
*/
private void calculateStartAndEndRow() {
this.startRow = this.pageNum > 0 ? (this.pageNum - 1) * this.pageSize : 0;
this.endRow = this.startRow + this.pageSize * (this.pageNum > 0 ? 1 : 0);
}
// 省略其他代码
还有一些代码我没贴,比如PageInterceptor#intercept方法,这里我整理了一下它的执行流程图,如下: