缘起

在项目增加新的功能模块的时候,对于后端即接口,根据数据库表结构一顿闷头敲代码。之后对接接口的时候,前端反馈了一个比较奇怪的问题:列表分页不管用,下拉刷新更新下一页内容,明明应该没有下一页数据,但结果却正常返回了最后一页的数据。

使用 postman 测试确认了问题:如果是访问最大页数之前的分页内容,数据正常返回;如果当前页超过了最大页数,数据返回最后一页的数据

分页调用示例代码:

PageHelper.startPage(page, size);
List<User> userList = userMapper.selectUserList();
return PageInfo.of(userList);

调用其他的分页列表接口,发现这并不是调用错误。本地调试确认传递进入的参数没有问题,但日志里显示的分页 sql 查询的是有数据的最后一页,返回的分页信息里 pageNum 显示也是最后一页的页码。似乎 pagehelper 对传入的参数做了一个校验修正。

当然这并不影响正常的使用,后台是显示所有的可用页码,并且返回的分页信息里也有 hasNextPage 是否有下一页字段可供判断,所以前端可以处理好这个问题。但还是好奇到底是哪里出现了问题。

研究过程

查看引入的依赖,主要是 tk-mybatis(作者更愿意叫 MyBatis 通用 Mapper,属于 MyBatis 的扩展、增强框架)和 pagehelper。因为对两个依赖不太熟悉,不确定是否是版本的问题,所以决定将两个依赖放到新建的测试项目中本地调试看看。

为控制变量,选择与原项目相同版本的依赖:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>xxx</version>
</dependency>

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

复制原项目中针对 mybatis、mapper、pagehelper 的一些配置项:

#mybatis
mybatis:
  # 搜索指定包别名
  typeAliasesPackage: com.seasidecrab.tkmybatis
  mapper-locations: classpath:mapper/**/*Mapper.xml
  mybatis.configuration.map-underscore-to-camel-case: true
  mybatis.configuration.log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  # 加载全局的配置文件
  config-Location: classpath:mybatis/mybatis-config.xml

mapper:
  not-empty: true
  identity: MYSQL

# PageHelper分页插件
pagehelper:
  helperDialect: mysql
  reasonable: true
  supportMethodsArguments: true
  params: count=countSql

创建好相关的 service、mapper 类与 xml 文件之后,按照上面的 分页调用示例代码 进行测试。具体的操作方法参考pagehelper 官方文档

测试复现了返回页数据重复问题:

列表返回数据截图

列表返回数据截图2

接下来是控制变量,对比测试:

  • 将 mapper-spring-boot-starter 升级到最新 4.2.3,没有变化
  • 将 pagehelper-spring-boot-starter 升级到最新 2.1.0,没有变化
  • 将 mapper-spring-boot-starter 替换成 mybatis-spring-boot-starter 最新版 3.0.3,修改 mapper 引入方式以及使用了 tk 的注解,报错:java.lang.UnsupportedClassVersionError: org/mybatis/spring/boot/autoconfigure/MybatisLanguageDriverAutoConfiguration has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 52.0,根据意思猜测是 java 1.8 版本有点低
  • 将 mapper-spring-boot-starter 替换成 mybatis-spring-boot-starter 次新版 2.3.2,没报错,但也没有变化
  • 感觉不对头,方向似乎搞错了,应该调整 pagehelper 而非 mybatis。将 pagehelper-spring-boot-starter 替换成原初的 pagehelper 最新版 6.1.0,按照 pagehelper 官方文档在 MyBatis 配置 xml 中配置拦截器插件,成功了。

成功后的返回(也是正确的返回):

正确的列表返回数据截图

跟进解决

pagehelper-spring-boot-starter 是对于 pagehelper 的一个包装依赖,正常来说,不会存在这样的 bug。到 github 的 issues 中翻了一下,找到了一个合理的解答:pageNum超出totalPages,分页总是返回最后一页的数据 #157。贡献者回复:

查看分页合理化参数,设置false即可。

pagehelper 官方文档 中找到这个配置参数说明:

reasonable:分页合理化参数,默认值为false。当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询。

检查前面的配置项,确实 reasonable: true。好嘛,原来是这个配置在搞事情,默认为 false,即不配置就不会有问题。在 pagehelper 没有生效,所以就没有问题。

下次遇到问题,不妨先去 github 上翻一翻 issues,说不定就有解决方法。