PageHelper 和 Mybatis-Plus 分页,两者都是基于 MyBatis 的拦截器机制来实现分页功能,通过拦截SQL语句并对其进行修改以实现分页。
PageHelper分页插件
在 MyBatis 中,当使用 PageHelper.startPage() 启动分页后,紧跟着的查询方法返回的 List 会被自动封装成一个 Page 对象。
使用
业务层直接调用 PageHelper.startPage(int pageNum, int pageSize) 传入分页参数(当前页码, 分页大小),PageHelper.startPage()之后紧跟查询方法即可。
PageHelper 的底层机制
基于 ThreadLocal 的分页参数传递
- 调用
startPage()时,分页参数(页码、每页条数等)会被存入当前线程的ThreadLocal变量中。 - 后续的 MyBatis 查询方法会被
PageInterceptor(分页拦截器)拦截,从ThreadLocal中读取分页参数。
拦截器修改 SQL 并封装结果
- 修改 SQL :拦截器根据分页参数,动态修改原始 SQL,添加数据库方言对应的分页语句(如 MySQL 的
LIMIT)。 - 执行
COUNT查询 :自动生成并执行SELECT COUNT(*)语句,获取总记录数。 - 执行
当前页查询:执行修改后的 SQL,获取当前页的数据。 - 封装
Page对象 :将分页数据和总记录数封装到Page对象中,最终返回给调用方。 - 清理ThreadLocal :分页完成后,PageHelper会自动清除ThreadLocal中的分页参数,避免影响后续无关的查询。
Tips:
拦截器会将分页查询写回
startPage()返回的Page对象中,即就是PageHelper.startPage()启动分页后,紧跟着的查询方法返回的List对象和startPage()返回的Page对象是同一个对象。见下图验证。
Page 和 PageInfo对象
Page 对象
-
本质 :
Page是ArrayList的子类,直接承载当前分页的数据集合(如当前页的查询结果列表)。 -
功能:
- 存储当前页的数据(继承自
List)。 - 包含基本的分页元数据,如:
pageNum:当前页码(从1开始)。pageSize:每页显示条数。total:总记录数(自动通过COUNT查询获取)。pages:总页数(由total和pageSize计算得出)。
- 存储当前页的数据(继承自
-
特点:
- 直接关联查询结果 :分页查询后的
List实际上是Page对象,可以直接强制转换获取分页元数据。 - 轻量级:仅包含当前页数据和基础分页信息。
- 直接关联查询结果 :分页查询后的
PageInfo 对象
-
本质 :
PageInfo是对Page的增强封装,提供更丰富的分页元数据和工具方法。 -
功能:
- 包含
Page的所有分页元数据(如pageNum、pageSize、total等)。 - 扩展了更多分页导航相关的属性,如:
isFirstPage/isLastPage:是否第一页/最后一页。hasPreviousPage/hasNextPage:是否有上一页/下一页。navigatePages:导航页码数量(如显示前5页、后5页)。navigatepageNums:导航页码数组(用于生成页码按钮)。prePage/nextPage:上一页/下一页的页码。
- 包含
-
特点:
- 脱离数据集合 :
PageInfo本身不直接存储数据集合,而是通过构造函数接收List(通常是Page对象)来解析元数据。 - 面向展示层 :更适合前端分页展示,直接提供导航所需的全部信息。
- 脱离数据集合 :
PageInfo可以通过任何List构造,是因为Page对象继承自ArrayList,但实际只有分页后的List(实际是Page对象)才能正确解析分页元数据。
Mybatis-Plus分页
MyBatis-Plus 的分页功能基于拦截器实现,需手动配置分页插件。
使用
- 创建
Page对象:封装分页参数(当前页、每页条数)。 - 执行分页查询 :调用MP提供的分页查询方法,传入
Page和查询条件。 - 获取分页结果 :返回的
Page对象包含数据和分页信息。
Java
public List<DeviceData> selectDeviceDataList(DeviceDataPageReqDto deviceData){
Page<DeviceData> deviceDataPage = new Page<>(deviceData.getPageNum(), deviceData.getPageSize());
this.lambdaQuery()
.eq(ObjUtil.isNotNull(deviceData.getDeviceId()), DeviceData::getIotId, deviceData.getDeviceId())
.eq(ObjUtil.isNotNull(deviceData.getFunctionId()), DeviceData::getFunctionId, deviceData.getFunctionId())
.between(ObjUtil.isNotNull(deviceData.getStartTime()) && ObjUtil.isNotNull(deviceData.getEndTime()),
DeviceData::getAlarmTime, deviceData.getStartTime(), deviceData.getEndTime())
// 执行分页查询
.page(deviceDataPage);
List<DeviceData> records = deviceDataPage.getRecords();
long total = deviceDataPage.getTotal();
// mapper层提供的分页查询方法示例
// Page<DeviceData> deviceDataPage1 = deviceDataMapper.selectPage(deviceDataPage, new LambdaQueryWrapper<>());
return records;
}
Tips:
只有在调用
MyBatis-Plus的提供的分页方法方法时,分页插件才会生效,自动拦截和处理分页逻辑。