1. MyBatis Plus分页拦截器核心痛点解析
作为Java后端开发中最常用的ORM框架组合,MyBatis Plus + Spring Boot在实际项目中处理分页查询时,开发者经常会遇到两个典型问题:
- 使用
Page对象进行分页查询时,返回的total字段始终为0 - 需要手动编写
count查询语句才能获取正确的总记录数
这本质上是因为MyBatis Plus的分页拦截器没有正确配置导致的。我们先看一个典型的问题代码示例:
java复制// 控制器层
@GetMapping("/users")
public Page<User> getUsers(Page<User> page) {
return userService.page(page);
}
// 服务层
@Override
public Page<User> page(Page<User> page) {
return baseMapper.selectPage(page, null);
}
执行后返回的JSON中total字段为0,而实际上数据库中有100条记录。这种问题在新手中出现频率极高。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 分页拦截器工作原理深度剖析
2.1 MyBatis Plus分页实现机制
MyBatis Plus的分页功能是通过拦截器(Interceptor)实现的,具体流程如下:
- 执行SQL前拦截:PaginationInnerInterceptor会拦截所有Mapper方法调用
- 自动生成count查询:根据原始SQL自动生成
SELECT COUNT(1)语句 - 改写原始SQL:添加数据库方言特定的分页语法(如LIMIT、ROWNUM等)
- 执行双重查询:先执行count查询获取总数,再执行分页查询获取当前页数据
- 封装Page对象:将结果集和total值封装到Page对象中返回
2.2 常见配置误区
导致分页失效的常见错误配置包括:
- 未注册分页拦截器到MyBatis配置中
- 在Spring Boot中未正确声明拦截器Bean
- 使用了多个SqlSessionFactory导致拦截器未生效
- 自定义的拦截器与分页拦截器执行顺序冲突
3. 完整解决方案实现
3.1 Spring Boot配置方案
在Spring Boot项目中,正确配置分页拦截器的方式如下:
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页拦截器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
关键点说明:
DbType需要根据实际数据库类型指定- 拦截器需要添加到
MybatisPlusInterceptor中,而不是直接注册到SqlSessionFactory - 在Spring Boot 2.7+版本中,这种配置方式最为可靠
3.2 传统MyBatis配置方案
对于非Spring Boot项目,需要在mybatis-config.xml中配置:
xml复制<configuration>
<plugins>
<plugin interceptor="com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor">
<property name="dbType" value="mysql"/>
</plugin>
</plugins>
</configuration>
3.3 高级配置参数
PaginationInnerInterceptor支持多个自定义参数:
java复制PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor();
pageInterceptor.setDbType(DbType.MYSQL);
// 设置最大单页限制数量,默认500
pageInterceptor.setMaxLimit(1000L);
// 开启count查询优化
pageInterceptor.setOptimizeJoin(true);
// 溢出总页数后是否进行处理
pageInterceptor.setOverflow(true);
4. 实战问题排查指南
4.1 常见问题及解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| total始终为0 | 拦截器未生效 | 检查拦截器是否正确配置 |
| 分页SQL语法错误 | 数据库方言设置错误 | 确认DbType与实际数据库匹配 |
| 性能低下 | 没有使用count优化 | 设置optimizeJoin=true |
| 内存溢出 | 未设置maxLimit | 限制单页最大记录数 |
4.2 性能优化建议
- 对于复杂联表查询,建议自定义count查询:
java复制@Select("SELECT * FROM user ${ew.customSqlSegment}")
@Select("SELECT COUNT(1) FROM user ${ew.customSqlSegment}")
Page<User> selectPageWithCustomCount(@Param("page") Page<User> page, @Param("ew") Wrapper<User> wrapper);
- 启用count查询缓存:
java复制pageInterceptor.setCountSqlParser(cache -> cache.computeIfAbsent(sql, key -> {
// 自定义缓存逻辑
return ...;
}));
- 对于百万级数据分页,建议采用"上一页/下一页"模式而非传统分页
5. 多租户场景下的特殊处理
在Saas系统或多租户架构中,分页查询需要额外注意:
- 确保分页查询自动带上租户条件
- 避免count查询漏掉租户过滤
- 推荐使用TenantLineInnerInterceptor与分页拦截器配合
配置示例:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 多租户拦截器
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(...));
// 分页拦截器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
6. 自定义分页逻辑实现
对于特殊分页需求,可以自定义分页处理器:
java复制public class CustomPaginationInterceptor extends PaginationInnerInterceptor {
@Override
protected void handlerTotal(boolean overflow, long total, long size, long current, IPage<?> page) {
// 自定义total处理逻辑
if(current > 1 && total == 0) {
page.setTotal(page.getTotal() * current);
}
}
}
7. 版本兼容性注意事项
不同MyBatis Plus版本的分页实现有差异:
- 3.4.x版本:推荐使用PaginationInnerInterceptor
- 3.3.x版本:使用PaginationInterceptor(已废弃)
- 3.2.x及以下:分页功能较为基础,建议升级
在Spring Boot 3.x环境中使用时,需要确保MyBatis Plus版本在3.5.3+以上以获得最佳兼容性。
