MyBatis 实现分页查询

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.startPagecount 参数控制。

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 配置文件中配置拦截器
  • 分页参数接收 :前端通常传 pageNumpageSize,后端用 PageHelper.startPage(pageNum, pageSize) 即可
  • 返回给前端的数据结构 :推荐 { total, pages, pageNum, pageSize, list } 这种标准格式
相关推荐
山甫aa1 天前
Javaweb---MyBatis增删改查
java·数据库·后端·mybatis·springboot
choumou_M1 天前
SpringBoot_2:MyBatis-Generator插件使用教程
spring boot·tomcat·mybatis
SQL-First布道者1 天前
全面解构传统持久层框架,拥抱真正的 SQL-First
java·数据库·spring boot·sql·spring·mybatis·spring jdbc
晚安code3 天前
MyBatis-Plus 分页插件与字段自动填充:两行配置告别 CRUD 重复劳动
mybatis
SQL-First布道者4 天前
持久层框架的评价标准:只有一个
java·spring boot·spring·tomcat·mybatis·spring jdbc
步行cgn4 天前
MyBatis 高级映射与延迟加载完全指南
java·tomcat·mybatis
mashirro4 天前
记一次基于ruoyi-vue新增小程序服务模块,小程序登陆相关
vue.js·小程序·mybatis
边境悍匪4 天前
蜗牛学苑 Java 智能体学习 Day24|SpringBoot 整合 MyBatis、分层解耦思维导图复盘
java·数据库·spring boot·学习·mybatis