Spring Boot项目中PageHelper分页失效的五大陷阱与实战解决方案

最近在重构一个老项目时,我又一次遇到了PageHelper分页失效的问题。这已经是第三次了,每次都能让我在调试时抓狂半小时。PageHelper作为MyBatis最流行的分页插件,用起来简单,但一旦出问题,排查起来却让人头疼。本文将分享我在实际项目中遇到的五种典型分页失效场景,以及如何快速定位和解决这些问题。

1. 依赖版本冲突:最隐蔽的坑

很多开发者在使用PageHelper时,第一个踩到的坑就是依赖版本不匹配。Spring Boot项目中有多个PageHelper相关的依赖,如果版本不一致,分页功能可能完全失效。

<!-- 错误示例:版本混用 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.3.2</version>
</dependency>
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.1</version>
</dependency>

推荐做法 是使用starter依赖,它会自动管理所有相关依赖的版本:

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.1</version>
</dependency>

如果必须单独引入,请确保所有PageHelper相关依赖版本一致。我曾经遇到过一个项目,pagehelper-core和pagehelper-spring-boot版本不一致,导致分页拦截器根本没有被注册。

2. 配置顺序问题:分页拦截器的正确打开方式

PageHelper的工作原理是通过MyBatis的拦截器机制实现的。如果配置不当,拦截器可能无法正确拦截SQL语句。

在Spring Boot中,有几种常见的配置方式:

  1. application.yml配置 (推荐):
pagehelper:
  helper-dialect: mysql
  reasonable: true
  support-methods-arguments: true
  1. Java Config方式
@Bean
public PageInterceptor pageInterceptor() {
    PageInterceptor pageInterceptor = new PageInterceptor();
    Properties properties = new Properties();
    properties.setProperty("helperDialect", "mysql");
    properties.setProperty("reasonable", "true");
    pageInterceptor.setProperties(properties);
    return pageInterceptor;
}

关键点 :确保配置在SqlSessionFactory创建之前完成。我曾经在一个多数据源项目中,因为配置顺序问题,导致主数据源的分页失效。

3. 多数据源环境下的特殊处理

在多数据源项目中,PageHelper的配置需要特别注意。每个SqlSessionFactory都需要单独配置分页拦截器。

@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource);
    
    // 添加PageInterceptor
    PageInterceptor pageInterceptor = new PageInterceptor();
    Properties properties = new Properties();
    properties.setProperty("helperDialect", "mysql");
    factoryBean.setPlugins(pageInterceptor);
    
    return factoryBean.getObject();
}

常见错误

  • 只在主数据源配置了分页拦截器
  • 不同数据源使用了不同的方言配置
  • 拦截器实例被多个SqlSessionFactory共享

4. 分页方法调用时机:PageHelper.startPage的陷阱

PageHelper.startPage()方法必须在查询方法调用之前执行,而且只对紧随其后的第一个查询有效。

// 正确用法
public PageInfo<User> getUsers(int pageNum, int pageSize) {
    PageHelper.startPage(pageNum, pageSize);
    List<User> users = userMapper.selectAll();
    return new PageInfo<>(users);
}

// 错误用法1:startPage在查询之后
public PageInfo<User> getUsersWrong1(int pageNum, int pageSize) {
    List<User> users = userMapper.selectAll();
    PageHelper.startPage(pageNum, pageSize); // 太晚了!
    return new PageInfo<>(users);
}

// 错误用法2:多个查询
public PageInfo<User> getUsersWrong2(int pageNum, int pageSize) {
    PageHelper.startPage(pageNum, pageSize);
    List<User> users1 = userMapper.selectAll(); // 这个会被分页
    List<User> users2 = userMapper.selectActive(); // 这个不会
    return new PageInfo<>(users2); // 返回的是未分页的结果
}

实战技巧 :在Service方法的最开始调用startPage,并且确保方法中只有一个查询。

5. SQL语句与分页的兼容性问题

不是所有的SQL语句都能很好地与PageHelper配合工作。以下是一些需要注意的情况:

  1. UNION查询 :PageHelper对UNION查询的支持有限,可能需要手动处理分页
  2. 嵌套查询 :复杂的嵌套查询可能导致分页计算错误
  3. 存储过程 :PageHelper无法拦截存储过程调用
  4. 手动分页参数 :SQL中已经包含LIMIT语句

解决方案

  • 对于复杂查询,考虑拆分为多个简单查询
  • 使用PageHelper的 PageMethod 进行手动分页控制
  • 对于特殊场景,可能需要放弃PageHelper,使用MyBatis提供的RowBounds
// 手动分页示例
public List<User> getUsersManual(int pageNum, int pageSize) {
    Page<User> page = PageMethod.startPage(pageNum, pageSize);
    userMapper.selectAll();
    return page;
}

调试技巧与最佳实践

当分页失效时,可以按照以下步骤排查:

  1. 检查是否真的调用了PageHelper.startPage()
  2. 查看MyBatis日志,确认是否生成了分页SQL
  3. 检查拦截器是否被正确注册
  4. 确认没有其他拦截器干扰PageHelper的工作

最佳实践

  • 统一使用PageHelper的starter依赖
  • 保持配置简单,避免过度定制
  • 为分页方法添加清晰的文档注释
  • 编写单元测试验证分页行为
@Test
public void testPagination() {
    // 第一页,每页10条
    PageHelper.startPage(1, 10);
    List<User> users = userMapper.selectAll();
    PageInfo<User> pageInfo = new PageInfo<>(users);
    
    assertEquals(10, users.size());
    assertTrue(pageInfo.isIsFirstPage());
    assertFalse(pageInfo.isIsLastPage());
}

分页是Web开发中最常见的功能之一,也是容易出错的环节。理解PageHelper的工作原理,避免这些常见陷阱,可以节省大量调试时间。在实际项目中,我建议建立一个分页工具类,封装常见的分页操作,减少重复代码和出错机会。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐