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

9 阅读3分钟

在苍穹外卖项目中,用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<br/>page=1, pageSize=10
    Service->>PageHelper: startPage(1, 10)
    Note over PageHelper: ThreadLocal存储分页参数
    
    Service->>Mapper: pageQuery(EmployeePageQueryDTO)
    Mapper->>Interceptor: 执行查询
    Note over Mapper,Interceptor: 原始SQL:<br/>SELECT * FROM employee<br/>ORDER BY create_time DESC
    
    Interceptor->>PageHelper: 拦截SQL请求
    PageHelper->>Interceptor: 改写SQL + 添加LIMIT
    Note over PageHelper: SQL转换:<br/>原始 → SELECT * FROM employee ...<br/>改写 → SELECT * FROM employee ... LIMIT 0,10
    
    Interceptor->>DB: 执行COUNT查询
    Note over Interceptor,DB: 自动生成COUNT SQL:<br/>SELECT COUNT(*) FROM employee
    DB-->>Interceptor: 返回总数: 100
    
    Interceptor->>DB: 执行分页查询
    Note over Interceptor,DB: 执行分页SQL:<br/>SELECT * FROM employee<br/>ORDER BY create_time DESC<br/>LIMIT 0,10
    DB-->>Interceptor: 返回10条员工数据
    
    Interceptor->>Mapper: 返回Page<Employee>对象
    Note over Interceptor,Mapper: Page对象包含:<br/>- 数据列表(10条)<br/>- 总数(100)<br/>- 分页信息
    
    Mapper-->>Service: Page<Employee>
    Service->>Service: 构建PageResult
    Note over Service: PageResult:<br/>total=100, records=[...]
    
    Service-->>Frontend: Result<PageResult>
    
    Note over PageHelper: 自动清理ThreadLocal分页参数

引言:分页就像翻书

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

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

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

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

-- 需要手动计算偏移量
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查询获取总数:

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层设置分页
@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层定义接口
/**
 * 分页查询员工
 * @param employeePageQueryDTO
 */
Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
步骤3:XML中编写业务SQL
<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: SELECT * FROM employee ORDER BY create_time DESC
改写后: SELECT * FROM employee ORDER BY create_time DESC LIMIT 0, 10

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

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查询获取总记录数,无需开发者手动编写:

SELECT COUNT(*) FROM employee

3.3 返回完整的Page对象

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

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

四、最佳实践建议

4.1 配置建议

application.yml中配置PageHelper:

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 避坑

// ❌ 错误:分页设置后执行了其他查询
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