MyBatis 实现分页查询。
分页是开发中的高频需求,MyBatis 本身提供了基础支持,但结合不同的场景和工具,可以有几种不同的实现方式。
一、最基础的方式:手动拼接 LIMIT
这是最原始、最直接的方式,就是直接在 Mapper XML 里写 LIMIT #{offset}, #{pageSize}。
Mapper 接口:
java
List<User> selectUserPage(@Param("offset") int offset,
@Param("pageSize") int pageSize);
Mapper XML:
xml
<select id="selectUserPage" resultType="User">
SELECT * FROM user
ORDER BY id DESC
LIMIT #{offset}, #{pageSize}
</select>
Service 层调用:
java
int pageNum = 2; // 第2页
int pageSize = 10; // 每页10条
int offset = (pageNum - 1) * pageSize;
List<User> list = userMapper.selectUserPage(offset, pageSize);
这种方式的优点是完全可控,SQL 清晰。缺点是:
- 每个需要分页的方法都要手动写 offset
- 还要单独写一个查询总记录数的 SQL
- 维护成本高,尤其是多条件查询时
二、进阶方式:使用 RowBounds(逻辑分页)
MyBatis 提供了 RowBounds 类,可以在不修改 SQL 的情况下实现分页。
Mapper 接口:
java
List<User> selectAllUsers();
XML 和之前一样,不需要写 LIMIT:
xml
<select id="selectAllUsers" resultType="User">
SELECT * FROM user ORDER BY id DESC
</select>
调用时传入 RowBounds:
java
int pageNum = 2;
int pageSize = 10;
int offset = (pageNum - 1) * pageSize;
RowBounds rowBounds = new RowBounds(offset, pageSize);
List<User> list = userMapper.selectAllUsers(rowBounds);
⚠️ 注意:这种方式是逻辑分页,不是物理分页。 MyBatis 会把所有数据查出来,然后在 Java 内存中截取指定范围的数据。数据量大的时候非常危险,容易 OOM。所以这种方式只适合数据量很小的场景,几乎不推荐在生产环境使用。
三、最推荐的方式:MyBatis PageHelper 插件
这是国内最主流、最成熟的分页方案,它在执行 SQL 之前自动拦截,把你的查询 SQL 改写成带 LIMIT 的物理分页 SQL,并且自动帮你查总记录数。
1. 引入依赖(Maven)
xml
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.7</version>
</dependency>
如果是非 Spring Boot 项目,用:
xml
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.3</version>
</dependency>
2. 配置拦截器(Spring Boot 方式会自动配置)
如果是非 Spring Boot 项目,需要在 MyBatis 配置文件中添加:
xml
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
3. 使用方式(极简)
Mapper 接口正常写,不需要动:
java
List<User> selectAllUsers();
Service 调用:
java
// 第2页,每页10条
PageHelper.startPage(2, 10);
List<User> list = userMapper.selectAllUsers();
// 获取分页信息
PageInfo<User> pageInfo = new PageInfo<>(list);
System.out.println("总记录数:" + pageInfo.getTotal());
System.out.println("总页数:" + pageInfo.getPages());
System.out.println("当前页数据:" + list);
PageHelper 的工作原理是在执行 Mapper 方法之前,通过拦截器改写 SQL,自动加上 LIMIT,并且额外执行一条 SELECT COUNT(*) 的 SQL 获取总记录数。
带条件查询的分页示例:
java
PageHelper.startPage(pageNum, pageSize);
List<User> list = userMapper.selectUsersByName("%张%");
PageInfo<User> pageInfo = new PageInfo<>(list);
四、进阶用法:排序
PageHelper 支持在分页时同时指定排序:
java
PageHelper.startPage(pageNum, pageSize, "id desc");
// 或者
PageHelper.startPage(pageNum, pageSize).setOrderBy("id desc");
五、注意事项
1. PageHelper.startPage 必须紧跟在 Mapper 方法之前
java
// ✅ 正确
PageHelper.startPage(1, 10);
List<User> list = userMapper.selectAll();
// ❌ 错误:中间有别的操作
PageHelper.startPage(1, 10);
someOtherMethod(); // 这会破坏分页上下文
List<User> list = userMapper.selectAll(); // 分页可能失效
2. 只对第一条 Mapper 查询生效
PageHelper 的分页参数只会作用于紧跟着的第一条 SQL 查询。如果你在一个方法中执行了两次查询,第二次不会分页。
3. 多表关联查询时 COUNT 语句可能不准确
如果查询有复杂的 JOIN,PageHelper 自动生成的 COUNT 语句可能不是最优的。可以通过在 XML 中单独写 <select id="count..." /> 来解决,或者在查询中使用 PageHelper.startPage 的 count 参数控制。
4. 返回 PageInfo 而不是直接返回 List
推荐用 PageInfo 封装结果,因为它包含了总页数、总记录数、当前页码等完整的元数据,方便前端处理。
java
PageInfo<User> pageInfo = new PageInfo<>(list);
Map<String, Object> result = new HashMap<>();
result.put("total", pageInfo.getTotal());
result.put("pages", pageInfo.getPages());
result.put("pageNum", pageInfo.getPageNum());
result.put("pageSize", pageInfo.getPageSize());
result.put("list", pageInfo.getList());
六、基于 XML 的 COUNT 优化
如果自动生成的 COUNT SQL 性能不佳,可以手动指定:
java
PageHelper.startPage(1, 10);
List<User> list = userMapper.selectUsersWithJoin();
然后在 XML 中通过 count 属性指定:
xml
<select id="selectUsersWithJoin" resultType="User" count="true">
SELECT u.*, d.name as deptName
FROM user u
LEFT JOIN dept d ON u.dept_id = d.id
WHERE u.status = 1
</select>
或者直接用注解:
java
@Select("SELECT * FROM user")
@Options(countQuery = "SELECT COUNT(*) FROM user")
List<User> selectAll();
七、三种方式对比总结
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 手动 LIMIT | 完全可控,无额外依赖 | 重复代码多,需要单独写 COUNT | 简单项目或特殊复杂查询 |
| RowBounds | 简单,无需改 SQL | 逻辑分页,数据量大时 OOM | 数据量极小的场景(几乎不用) |
| PageHelper | 自动物理分页,自动 COUNT,零侵入 | 需要引入依赖,需要注意使用顺序 | 绝大多数项目推荐 |
八、终极建议
- Spring Boot 项目 :直接用
pagehelper-spring-boot-starter - 非 Spring Boot 项目 :引入
pagehelper,在 MyBatis 配置文件中配置拦截器 - 分页参数接收 :前端通常传
pageNum和pageSize,后端用PageHelper.startPage(pageNum, pageSize)即可 - 返回给前端的数据结构 :推荐
{ total, pages, pageNum, pageSize, list }这种标准格式