PageHelper 与 MyBatis 的分页查询协作原理

在苍穹外卖项目中,用PageHelper实现了对员工的分页查询。来看看PageHelper是如何与Mybatis协作的!

协作时序图如下

sequenceDiagram participant Frontend as 前端 participant Service as Service层 participant PageHelper as PageHelper participant Mapper as Mapper participant Interceptor as MyBatis拦截器 participant DB as 数据库 Frontend->>Service: GET /admin/employee/page
page=1, pageSize=10 Service->>PageHelper: startPage(1, 10) Note over PageHelper: ThreadLocal存储分页参数 Service->>Mapper: pageQuery(EmployeePageQueryDTO) Mapper->>Interceptor: 执行查询 Note over Mapper,Interceptor: 原始SQL:
SELECT * FROM employee
ORDER BY create_time DESC Interceptor->>PageHelper: 拦截SQL请求 PageHelper->>Interceptor: 改写SQL + 添加LIMIT Note over PageHelper: SQL转换:
原始 → SELECT * FROM employee ...
改写 → SELECT * FROM employee ... LIMIT 0,10 Interceptor->>DB: 执行COUNT查询 Note over Interceptor,DB: 自动生成COUNT SQL:
SELECT COUNT(*) FROM employee DB-->>Interceptor: 返回总数: 100 Interceptor->>DB: 执行分页查询 Note over Interceptor,DB: 执行分页SQL:
SELECT * FROM employee
ORDER BY create_time DESC
LIMIT 0,10 DB-->>Interceptor: 返回10条员工数据 Interceptor->>Mapper: 返回Page对象 Note over Interceptor,Mapper: Page对象包含:
- 数据列表(10条)
- 总数(100)
- 分页信息 Mapper-->>Service: Page Service->>Service: 构建PageResult Note over Service: PageResult:
total=100, records=[...] Service-->>Frontend: Result Note over PageHelper: 自动清理ThreadLocal分页参数

引言:分页就像翻书

想象一下,你有一本1000页的员工花名册,每次查看时,你不需要一次性翻阅所有页面,而是根据目录找到对应的页码,每次只查看10-20页。这就是分页的核心思想------按需加载,提升效率

在Spring Boot + MyBatis项目中,实现分页查询往往需要手动编写复杂的SQL语句,计算偏移量,处理总数统计。而PageHelper的出现,让这一切变得异常简单。

一、问题背景:传统分页的痛点

sky-take-out项目的员工管理模块中,我们需要实现员工列表的分页查询。传统做法需要:

sql 复制代码
-- 需要手动计算偏移量
SELECT * FROM employee 
ORDER BY create_time DESC 
LIMIT 0, 10;  -- 第1页

SELECT * FROM employee 
ORDER BY create_time DESC 
LIMIT 10, 10; -- 第2页

还需要单独执行COUNT查询获取总数:

sql 复制代码
SELECT COUNT(*) FROM employee;

这种模式不仅代码冗余,而且容易出错。

二、PageHelper:分页的"智能管家"

2.1 核心原理

PageHelper是一个MyBatis分页插件,它通过拦截器机制在运行时自动处理分页逻辑:

  1. 设置分页参数PageHelper.startPage(pageNum, pageSize)
  2. 拦截SQL:自动在查询前拦截
  3. 改写SQL:添加LIMIT子句
  4. 执行COUNT:自动生成并执行COUNT查询
  5. 返回Page对象:包含数据列表和分页信息

2.2 在员工查询中的应用

让我们看看sky-take-out项目中如何优雅地使用PageHelper:

步骤1:Service层设置分页
java:102-110:sky-server/src/main/java/com/sky/service/impl/EmployeeServiceImpl.java 复制代码
@Override
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
    // 关键一行:设置分页参数
    PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());
    
    // 正常执行Mapper查询
    Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);
    
    // 返回封装好的分页结果
    return new PageResult(page.getTotal(), page.getResult());
}
步骤2:Mapper层定义接口
java:34-38:sky-server/src/main/java/com/sky/mapper/EmployeeMapper.java 复制代码
/**
 * 分页查询员工
 * @param employeePageQueryDTO
 */
Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
步骤3:XML中编写业务SQL
xml:6-15:sky-server/src/main/resources/mapper/EmployeeMapper.xml 复制代码
<select id="pageQuery" parameterType="EmployeePageQueryDTO" resultType="com.sky.entity.Employee">
  SELECT * FROM employee
  <where>
    <if test="name != null and name != ''">
      AND name like concat('%', #{name}, '%')
    </if>
  </where>
  ORDER BY create_time DESC
</select>

注意:这里完全不需要关心分页逻辑,只需编写业务查询!

三、PageHelper的魔法时刻

3.1 自动SQL改写

当你调用employeeMapper.pageQuery()时,PageHelper在幕后执行了以下魔法:

sql 复制代码
原始SQL: SELECT * FROM employee ORDER BY create_time DESC
改写后: SELECT * FROM employee ORDER BY create_time DESC LIMIT 0, 10

从日志中可以看到实际效果:

sql 复制代码
2026-04-26 00:03:12.142 DEBUG: ==> Preparing: 
SELECT * FROM employee ORDER BY create_time DESC LIMIT ?
2026-04-26 00:03:12.142 DEBUG: ==> Parameters: 10(Integer)

3.2 智能COUNT查询

PageHelper会自动执行COUNT查询获取总记录数,无需开发者手动编写:

sql 复制代码
SELECT COUNT(*) FROM employee

3.3 返回完整的Page对象

查询结果被封装在Page<Employee>对象中,包含:

  • page.getResult(): 当前页的员工列表
  • page.getTotal(): 总员工数
  • page.getPageNum(): 当前页码
  • page.getPageSize(): 每页大小
  • page.getPages(): 总页数

四、最佳实践建议

4.1 配置建议

application.yml中配置PageHelper:

yaml 复制代码
pagehelper:
  helper-dialect: mysql
  reasonable: true
  support-methods-arguments: true

4.2 使用规范

  1. 紧邻查询调用PageHelper.startPage()应紧邻Mapper调用
  2. 异常处理:确保在finally块或AOP中清理ThreadLocal
  3. 参数验证:验证page和pageSize的合法性

4.3 避坑

java 复制代码
// ❌ 错误:分页设置后执行了其他查询
PageHelper.startPage(1, 10);
someOtherMapper.query(); // 这会受到影响!
employeeMapper.pageQuery();

// ✅ 正确:分页设置后立即执行目标查询
PageHelper.startPage(1, 10);
employeeMapper.pageQuery();

五、总结

PageHelper的出现,让MyBatis分页从"体力活"变成了"智能活"。它就像一位贴心的助手,在你需要分页时默默处理好一切,让你可以专注于业务逻辑的实现。

sky-take-out项目中,正是PageHelper的加持,让员工分页查询变得如此简洁优雅。下次当你需要实现分页时,不妨试试PageHelper,体验一下"一行代码搞定分页"的快感!

参考

苍穹外卖www.bilibili.com/video/BV1TP...

deekseek-v4

相关推荐
卷无止境2 小时前
AI编程时代,什么需求使用rust开发最合适?
后端
lagrahhn3 小时前
ES索引的基础和进阶内容
后端·elasticsearch·搜索引擎
SamDeepThinking3 小时前
秒杀系统怎么区分真实用户和黄牛脚本?
java·后端·架构
stark张宇3 小时前
深入Go运行时:数值溢出、浮点精度与栈堆分配决策
后端·go
fliter3 小时前
Rust 里最让人头疼的两个类型:Pin 和 Unpin,究竟解决了什么问题?
后端
覆东流3 小时前
第7天:Python小项目
开发语言·后端·python
码化豚3 小时前
揭秘外卖平台城市区域地理围栏/电子围栏设计
后端
xiaogg36783 小时前
springcloud oauth2 自定义token实现
spring boot·后端·spring cloud
pixcarp3 小时前
Nginx实战部署与踩坑总结 附带详细配置教程
服务器·前端·后端·nginx·golang