05. 插件生态:PageHelper 分页查询¶
🛍️ 想象一下:如果你打开淘宝...
如果淘宝把所有 10 亿件商品一次性全部加载到你的手机屏幕上,会发生什么?
- 📱 手机爆炸:内存溢出(OOM)。
- 🐌 网络瘫痪:下载这几 TB 数据需要好几年。
- 💥 老板发火:数据库服务器瞬间崩溃。
所以,“分页查询”(每次只查 10 条)是后端开发的生存必备技能。
在 JDBC 时代,我们需要手动计算 LIMIT offset, size,极其繁琐。今天,我们将引入 PageHelper,它能让你彻底告别 SQL 分页语法,用一行代码实现“魔法”分页。
🆚 第一步:物理分页 vs 逻辑分页 (面试必问)¶
在学习插件之前,必须懂原理。实现分页有两种流派:
| 特性 | 逻辑分页 (内存分页) | 物理分页 (数据库分页) |
|---|---|---|
| 原理 | 把所有数据(如 100万条)查到内存,再截取第 10-20 条。 | 在 SQL 末尾加 LIMIT,数据库只返回这 10 条。 |
| 优点 | 跨数据库通用。 | 性能极高,内存占用极小。 |
| 缺点 | 性能灾难!数据量大时直接 OOM。 | 不同数据库语法不同 (MySQL 用 LIMIT, Oracle 用 ROWNUM)。 |
| 结论 | ❌ 坚决抵制 | ✅ 企业级标准 |
PageHelper 的核心价值: 它让我们在 Java 代码中写起来像“逻辑分页”一样简单,但底层自动帮我们转换成最高效的“物理分页” SQL。
⚙️ 第二步:PageHelper 魔法原理¶
为什么我们不需要写 LIMIT 还能实现分页?因为 PageHelper 使用了 MyBatis 拦截器 (Interceptor) 机制。
sequenceDiagram
participant User as 你的代码
participant Plugin as PageHelper拦截器
participant DB as 数据库
User->>Plugin: 1. startPage(1, 10) <br/>(参数存入 ThreadLocal)
User->>Plugin: 2. 调用 findAll() 查询
Plugin->>Plugin: 3. 拦截 SQL <br/>检测到 ThreadLocal 有分页参数
Plugin->>DB: 4. 自动执行 SELECT COUNT(*)...
Plugin->>DB: 5. 自动执行 SELECT ... LIMIT 10 OFFSET 0
DB-->>User: 6. 返回分页后的数据
Plugin->>Plugin: 7. 清理 ThreadLocal
📦 第三步:引入依赖¶
PageHelper 专门为 Spring Boot 提供了 Starter 包。
注意:Spring Boot 3 + JDK 17 环境下,请务必使用 1.4.6 及以上版本(或适配 SB3 的最新版 2.1.0+),避免循环依赖报错。
| pom.xml | |
|---|---|
Maven 刷新提醒
修改 pom.xml 后,别忘了点击 IDEA 右上角的 "Load Maven Changes" (小刷新图标),或者按 Ctrl + Shift + O。
⚡ 第四步:Service 层实战 (核心)¶
这是 PageHelper 最核心的用法,请务必背诵这 "三部曲"。我们不需要修改 Mapper 里的任何 SQL!
📡 第五步:Controller 层响应¶
前端通常需要知道“一共有多少页”、“当前是第几页”。PageInfo 对象完美封装了这些数据。
| UserController.java | |
|---|---|
前端收到的 JSON 结构:
⚠️ 避坑指南 (严厉警告)¶
PageHelper 非常好用,但有两个致命隐患,新手极容易翻车:
1. 致命死穴:分页污染¶
PageHelper.startPage() 使用 ThreadLocal 传递参数。它只对紧随其后的 第一条 SQL 语句生效!
- 场景:如果你调用了
startPage,但紧接着的代码抛出了异常,导致userMapper.findAll()没有执行。 - 后果:这个分页参数会残留在当前线程中。当这个线程被复用去处理下一个请求(比如查询全校班级)时,下一个请求明明没想分页,却莫名其妙被加上了
LIMIT 10。 - 对策:始终保持
startPage和mapper查询紧紧挨在一起,中间不要有任何逻辑代码。
2. 级联查询坑 (1对N)¶
- 场景:你在 XML 中使用
<collection>关联查询(比如“查班级同时也查出所有学生”)。 - 后果:PageHelper 是对结果集分页的。如果 1 个班级有 50 个学生,数据库返回 50 行数据。PageHelper
LIMIT 10后,可能连第 1 个班级的学生都没查全。 - 对策:复杂的多表关联分页,建议在 Service 层分步查询,或者手动手写 SQL 分页。
🤖 特别篇:AI 帮你重构代码¶
🚀 场景:老代码改造
有时候你接手了一个老项目,发现里面全是 findAll(),老板让你把它们全部改成即插即用的分页接口。手动改很累,交给 AI 吧!
- 👨💻 工程师职责:找到需要改造的 Service 方法。
- 🤖 AI 职责:生成分页封装代码。
您可以复制以下指令发送给 AI:
🔮 复制此 Prompt (提示词) 给 AI
"我正在使用 Spring Boot 和 PageHelper。请帮我把下面的普通查询方法,改造成分页查询方法。
原始代码:
[在此处粘贴你的 Service 代码]
要求:
1. 方法参数增加 int pageNum, int pageSize。
2. 使用 PageHelper.startPage 实现分页。
3. 返回类型改为 PageInfo<User>。
4. 特别注意:请解释为什么 PageHelper.startPage 必须写在 SQL 查询的第一行前面。"
📝 总结¶
| 核心类 | 作用 | 备注 |
|---|---|---|
PageHelper |
工具类 | 核心方法 startPage(page, size),开启拦截器。 |
PageInfo |
结果封装类 | 包含 total, list 等信息,直接返回给前端。 |
| 物理分页 | 核心思想 | 利用 SQL 的 LIMIT,保护数据库内存。 |
通关标志
如果你能通过修改 URL 中的 page 和 size 参数(例如 ?page=2&size=5),看到返回的数据随之变化,且 total 总数正确,说明你已经掌握了企业级分页开发的精髓!