MyBatis 分页

列表页永远要分页。分页有两种:逻辑分页先把符合条件的数据全部查出再截断(数据多了就崩);物理分页直接生成带 LIMIT/OFFSET 的 SQL,只查需要的行。MyBatis 核心自带的能力只有前者(RowBounds),生产环境的分页几乎都交给 PageHelper 这类插件做物理分页。

RowBounds:内置但够用吗

RowBounds 通过偏移量 + 条数描述"要哪一段",两种用法:

// 用法一:SqlSession API 传入
List<User> list = session.selectList("...UserMapper.selectAll",
        null, new RowBounds(0, 10));

// 用法二:Mapper 接口方法声明 RowBounds 参数
List<User> page(User user, RowBounds rowBounds);
<select id="selectAll" resultType="user">
    SELECT id, name, age FROM user
</select>

局限:MyBatis 核心不生成数据库方言的分页 SQL(如 MySQL 的 LIMIT),RowBounds 只是在结果层面做偏移截断——数据照查、照传,只是丢弃用不到的行(逻辑分页)。数据量大时性能差,这也是"RowBounds 只适合小数据量"的原因(细节以官方文档为准)。

PageHelper:物理分页主流方案

PageHelper 是第三方分页插件(官方仓库 mybatis-pagehelper),原理是第 12 章的 Executor 拦截器:拦截查询,自动在 SQL 后面拼上对应数据库的分页方言。

第一步:引入依赖

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.x</version>
</dependency>

第二步:注册拦截器(5.x 用 PageInterceptor,更早版本直接写 PageHelper 类名)

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <property name="helperDialect" value="mysql"/>
        <property name="reasonable" value="true"/>
    </plugin>
</plugins>

helperDialect 指定数据库(mysql/oracle/…);reasonable=true 时页码越界会自动修正到首/末页。

第三步:编码

// 1. 分页参数:只对紧随其后的第一条查询生效
PageHelper.startPage(1, 10);
List<User> list = userMapper.selectAll();

// 2. 返回的 List 实际是 Page,能直接拿到总数
long total = ((Page<User>) list).getTotal();

// 3. 更常用:包一层 PageInfo,页面需要的字段都齐了
PageInfo<User> pageInfo = new PageInfo<>(list);
System.out.println(pageInfo.getTotal()); // 总条数
System.out.println(pageInfo.getPages()); // 总页数
System.out.println(pageInfo.getList());  // 当前页数据

startPage 内部使用 ThreadLocal:调用后紧跟着执行的那一次 Mapper 查询会被分页,查询结束后参数自动清除。中途不要再发别的查询,也不要跨方法使用,否则容易分页错乱或漏分页。

Spring Boot 场景:可用 pagehelper-spring-boot-starter,在 application.yml 配置 pagehelper.helper-dialectpagehelper.reasonable 等(属性名与版本以官方文档为准)。

常见坑汇总

  • RowBounds 是逻辑分页,只适合数据量很小的场景。
  • startPage 与查询之间不能插入其他查询/逻辑。
  • count 查询会自动生成并执行,大表统计慢时可关注其 count SQL 的效率。
  • 分页插件会把"分页上下文"绑定到当前线程,线程池/异步场景要小心参数残留(按官方建议使用)。

小结

内置 RowBounds 只做结果截断,扛不住大数据量;物理分页用 PageHelper:注册 PageInterceptor → startPage 后紧跟查询 → 用 Page/PageInfo 取数。插件版本、数据库方言配置一律以官方文档为准。

笔记加载中…