一、什么是分页
1-1、传参
页码:pageNum(用户会发送请求,携带页码 pageNum 给服务器.)
每页显示的记录条数:pageSize,例如百度默认就是每页展示 10 条记录。
实际上每一次在进行分页请求发送的时候,都是要发送两个数据的:
- 页码 pageNum 要传送给服务器。
- 每页显示的记录条数 pageSize 也要传送给服务器。
前端提交表单的话,数据格式:
uri?pageNum=1&pageSize=10
1-2、关于mysql当中的分页sql应该怎么写?
limit关键字。
limit 语法格式:
limit 开始下标,显示的记录条数,
示例:limit startIndex, pageSize(从第startIndex条开始,显示pageSize条)
select * from t_car limit 0, 3;
select * from t_car limit 2; 和 select * from t_car limit 0, 2;是等效的。
mysql当中起始行的下标从0开始,即:第一条记录的下标是0。

二、使用 PageHelper 插件(推荐)
PageHelper 是 MyBatis 最常用的分页插件,它通过拦截器方式,在执行 SQL 前自动在语句后追加 LIMIT 和 OFFSET,从而达到分页的目的。
它的核心原理是:拦截
Executor.query()方法,对 SQL 动态改写。
1. 添加依赖(Maven)
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.2</version>
</dependency>
2、在 mybatis-config.xml中添加拦截器
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql" />
<property name="reasonable" value="true" />
</plugin>
</plugins>
配置结构说明
-
<plugins>是 MyBatis 插件扩展点 -
<plugin>是指注册一个分页拦截器(PageInterceptor) -
每个
<property>都是对插件行为的控制
(1)、helperDialect="mysql"
指定数据库方言,也就是 PageHelper 应该为哪种数据库生成分页语句。
| 值 | 对应数据库 |
|---|---|
mysql | MySQL、MariaDB |
oracle | Oracle |
mssql | SQL Server |
postgresql | PostgreSQL |
sqlite | SQLite |
建议:明确指定
mysql,避免自动识别出错。
(2)、reasonable="true"
是否启用 分页参数合理化机制,常用于防止越界问题。
启用后(true)的行为:
| 情况 | 结果 |
|---|---|
| pageNum ≤ 0 | 自动使用第一页(pageNum = 1) |
| pageNum > 最大页 | 自动使用最后一页 |
| pageSize ≤ 0 | 查所有记录 |
示例:
PageHelper.startPage(-1, 10); // 实际使用 pageNum = 1
PageHelper.startPage(9999, 10); // 如果最大页是 50,会用 pageNum = 50
推荐开启,防止前端传参错导致崩溃或空页。
在使用 PageHelper 时,它的页码参数(Page Number)是从 1 开始的。
因为,PageHelper 的拦截器会在底层自动帮你算出数据库真正需要的偏移量(Offset),计算公式是
(pageNum - 1) * pageSize。
3. 基本用法
你只需要在查询之前调用:

PageHelper是 MyBatis 拦截器。当你调用 PageHelper.startPage(pageNum, pageSize) 之后,紧随其后的那一次查询 会被它拦截:
-
自动改写 SQL 为带
LIMIT/OFFSET的分页 SQL; -
自动再跑一条
COUNT(*)统计总数; -
把返回的
List<T>包装成Page<T>(它继承ArrayList<T>,多了total/pageNum/pageSize/...等字段)。
因为方法签名通常写的是
List<T>,编译期你看到是List,运行期实际是Page;需要访问分页信息时,手动强转或用
PageInfo二次封装。
startPage 会自动修改 SQL,加上 LIMIT 和 OFFSET,配合 PageInfo 拿到分页数据和总页数等信息。
【注意】:
一定要在执行sql语句之前加上:PageHelper.startPage(pageNum, pageSize);
控制台执行的sql语句会自动加上limit分页

4. PageInfo 的使用(封装分页结果)
PageInfo<Car> pageInfo = new PageInfo<>(cars);
直接将查询结果给到pageInfo即可
| 方法 | 返回值 | 含义 |
|---|---|---|
getTotal() | long | 总记录数 |
getPages() | int | 总页数 |
getPageNum() | int | 当前页码 |
getPageSize() | int | 每页大小 |
getList() | List | 当前页的数据列表 |
isHasNextPage() | boolean | 是否有下一页 |
isIsLastPage() | boolean | 是否是最后一页 |
5、XML 文件中的配合使用
你的 XML 中写的仍是普通 SQL:

分页逻辑由 Java 代码调用
PageHelper.startPage()控制,XML 无需修改。
三、PageHelper 中 Page、PageInfo
| 名称 | 来源 | 用途 | 谁创建的 |
|---|---|---|---|
| Page | PageHelper 内部类 | MyBatis 查询结果的分页包装(实现了 List 接口) | PageHelper 自动生成 |
| PageInfo | PageHelper 提供的封装类 | 对 Page 进行二次封装,用于返回分页信息给前端 | 你手动 new |
1、执行过程理解
当你使用 PageHelper 分页查询时,执行顺序是:
PageHelper.startPage(pageNum, pageSize);
List<Article> list = articleMapper.list(...);
此时 list 实际类型是 Page<Article>。
接着你可以用两种不同方式去处理这个分页对象:
方式一:使用 PageInfo(PageHelper 官方推荐)
PageInfo<Article> pageInfo = new PageInfo<>(list);
PageInfo 会从 Page 对象中提取分页信息(比如 total、pages、pageNum、pageSize 等),
然后形成一个完整的结构:
{
"total": 96,
"pageNum": 1,
"pageSize": 10,
"list": [ ... ],
"pages": 10,
"hasNextPage": true,
...
}
这种方式比较通用,不用你自己定义 PageBean。
方式二:使用自定义 PageBean(更灵活、风格统一)
项目中很多人不想直接返回 PageInfo,因为:
-
PageInfo字段太多,前端不一定都需要; -
业务希望统一分页返回结构(比如只返回
total和items); -
避免直接暴露三方库类型。
于是就会这样写:
Page<Article> page = (Page<Article>) list;
PageBean<Article> pb = new PageBean<>();
pb.setTotal(page.getTotal());
pb.setItems(page.getResult());
最终返回给 Controller 或前端的就是你自己的封装。
2、总结三者关系
| 层次 | 对象 | 角色定位 | 你需要做什么 |
|---|---|---|---|
| 底层 | Page | MyBatis 查询结果 + 分页信息 | PageHelper 自动生成 |
| 中间层 | PageInfo | 对 Page 的通用封装 | 官方提供、可直接返回 |
| 上层 | PageBean | 你自己定义的业务封装 | 为项目返回结构统一 |
3、一句话总结
Page是 查询结果容器,PageInfo是 PageHelper 提供的结果封装类,PageBean是 你项目自定义的返回格式。
四、Page对象
list的实际运行时类型确实是Page<Article>,但编译器只知道它是List<Article>,所以如果你要访问分页信息(如 getTotal()),必须手动强转。
1、表面上看:返回的是 List<Article>
你调用的 Mapper 方法,比如:
List<Article> list = articleMapper.list(userId, categoryId, state);
在 Mapper XML 中,大概是:
<select id="list" resultType="Article">
SELECT * FROM article WHERE ...
</select>
这个方法的返回值声明是 List<Article>,所以编译器只知道:
“这玩意儿返回一个 List。”
2、PageHelper 干了什么?
当你调用:
PageHelper.startPage(pageNum, pageSize);
的时候,PageHelper 会通过 MyBatis 的拦截器机制(Interceptor)做两件事:
-
拦截 SQL 执行
它会在执行真正的 SQL 之前,修改你的 SQL 为分页版本,比如:SELECT * FROM article LIMIT 0, 10;同时再执行一条统计总数的 SQL:
SELECT COUNT(*) FROM article; -
拦截返回结果
它把 Mapper 返回的原始List<Article>包装成一个特殊的Page<Article>对象。
而Page本身就是ArrayList的子类:public class Page<E> extends ArrayList<E> implements Closeable { private long total; private int pageNum; private int pageSize; ... }所以
Page既是List,又多了分页信息(total、pages、pageNum等)。
3、为什么还需要强转?
因为:
-
编译器类型检查:
Mapper 的方法签名是List<Article>,所以编译器只认 List; -
运行时实际类型:
PageHelper 返回的实际对象是Page<Article>,只是子类对象赋值给了父类引用。
举个例子:
ArrayList<String> list = new ArrayList<>();
List<String> data = list; // ✅ 子类赋给父类引用
data.size(); // OK,List的方法
data.getTotal(); // ❌ 报错,List没有getTotal()
要想用 Page 的方法(如 getTotal()),必须:
Page<Article> page = (Page<Article>) list;
4、总结一句话记忆
PageHelper 用 AOP 拦截 把原来的
List包装成Page,
Page实际上是List的子类,
编译器不知道,运行时知道,
所以如果你要访问分页属性,必须强转。
5、可选方案:直接用 PageInfo(不需要强转)
PageHelper.startPage(pageNum, pageSize);
List<Article> list = articleMapper.list(...);
PageInfo<Article> pageInfo = new PageInfo<>(list);
System.out.println(pageInfo.getTotal());
System.out.println(pageInfo.getList());
PageInfo 会自动识别 list 是不是 Page 类型,不需要你强转,非常方便。

1095

被折叠的 条评论
为什么被折叠?



