1. 问题背景:MyBatis Plus分页为何频频失效?
上周排查一个线上问题时,发现某核心接口的分页功能完全失效——前端传了pageSize=10,结果SQL直接查出了全表数据。打开日志一看,控制台赫然打印着SELECT * FROM user WHERE...,根本没有自动拼接LIMIT子句。这已经是团队本月第三次遇到MyBatis Plus分页插件失效的问题了。
作为MyBatis增强工具包,MyBatis Plus的分页插件本应通过简单配置就能实现物理分页。但实际开发中,很多开发者(包括曾经的我)都会遇到分页配置不生效的情况。今天我们就彻底剖析这个问题,并给出真正可落地的解决方案。
2. 分页失效的四大常见原因
2.1 配置类未正确注入Spring容器
这是最常见的问题根源。分页插件需要通过@Configuration声明,但很多开发者会犯以下错误:
java复制// 错误示例1:忘记加@Configuration注解
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
// 错误示例2:配置类未被组件扫描到
@Configuration
public class MybatisPlusConfig {...}
// 但主启动类@ComponentScan未包含该包路径
避坑指南:用
@SpringBootApplication默认扫描路径,或显式指定@ComponentScan
2.2 分页拦截器顺序不当
当存在多个拦截器时,执行顺序会影响分页效果:
java复制// 错误示例:分页拦截器添加顺序不对
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 乐观锁
interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 分页
正确的顺序应该是分页拦截器在前:
java复制// 正确顺序
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 先分页
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 后乐观锁
2.3 分页参数传递方式错误
Controller层常见的参数传递错误:
java复制// 错误示例1:未使用IPage参数
@GetMapping("/list")
public List<User> list(Integer pageNum, Integer pageSize) {...}
// 错误示例2:Page对象手动new导致分页信息丢失
@GetMapping("/list")
public Page<User> list() {
return userService.page(new Page<>(1, 10)); // 硬编码分页参数
}
正确做法应该是:
java复制// 正确示例
@GetMapping("/list")
public IPage<User> list(@RequestParam(defaultValue = "1") long current,
@RequestParam(defaultValue = "10") long size) {
return userService.page(new Page<>(current, size));
}
2.4 自定义SQL未使用Page参数
在XML映射文件中,如果手写SQL但未保留分页参数:
xml复制<!-- 错误示例 -->
<select id="selectByCondition" resultType="User">
SELECT * FROM user WHERE ${ew.customSqlSegment}
</select>
应该确保接口方法接收Page参数:
java复制// Mapper接口
IPage<User> selectByCondition(Page<User> page, @Param(Constants.WRAPPER) Wrapper<User> wrapper);
3. 一行配置的真正正确姿势
3.1 基础配置模板
最精简且完整的配置方案:
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件(必须放在第一个)
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
关键点说明:
DbType指定数据库类型(支持MySQL、Oracle等)- 建议显式设置
dialectType避免自动检测开销
3.2 高级配置参数
针对不同场景可以调整的参数:
java复制PaginationInnerInterceptor pagination = new PaginationInnerInterceptor();
pagination.setDbType(DbType.MYSQL);
pagination.setOverflow(true); // 超出总页数后回到首页
pagination.setMaxLimit(1000L); // 单页最大记录数限制
interceptor.addInnerInterceptor(pagination);
4. 特殊场景解决方案
4.1 多数据源分页配置
在多数据源环境下,需要为每个数据源单独配置:
java复制@Bean
@ConfigurationProperties(prefix = "spring.datasource.ds1")
public DataSource ds1() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory ds1SqlSessionFactory() throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(ds1());
factory.setPlugins(ds1Interceptor()); // 关键点
return factory.getObject();
}
private Interceptor ds1Interceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
4.2 自定义分页SQL处理
对于复杂分页查询(如联表查询),可能需要手动处理:
java复制// Mapper接口
@Select("SELECT u.*, d.dept_name FROM user u LEFT JOIN dept d ON u.dept_id=d.id ${ew.customSqlSegment}")
IPage<UserDeptVO> selectUserDeptPage(IPage<?> page, @Param(Constants.WRAPPER) Wrapper<?> wrapper);
对应的XML配置:
xml复制<select id="selectUserDeptPage" resultType="UserDeptVO">
SELECT u.*, d.dept_name
FROM user u LEFT JOIN dept d ON u.dept_id=d.id
${ew.customSqlSegment}
</select>
5. 性能优化建议
5.1 避免COUNT查询
对于大表分页,可以通过page.setSearchCount(false)跳过COUNT查询:
java复制Page<User> page = new Page<>(current, size);
page.setSearchCount(false); // 不查询总记录数
IPage<User> result = userMapper.selectPage(page, queryWrapper);
5.2 分页合理化配置
java复制pagination.setOptimizeJoin(true); // 优化JOIN查询
pagination.setLimit(500L); // 默认单页大小
pagination.setCountId("selectCount"); // 指定COUNT方法名
6. 常见问题速查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 分页参数无效 | 1. 未配置拦截器 2. 参数类型错误 |
1. 检查@Configuration2. 使用 IPage参数 |
| 分页后总数不对 | 1. 自定义SQL有GROUP BY 2. 逻辑删除干扰 |
1. 手动实现COUNT 2. 检查逻辑删除条件 |
| 排序失效 | 1. 参数未URL编码 2. 字段名错误 |
1. 前端编码处理 2. 检查数据库字段 |
| 性能低下 | 1. 大表COUNT 2. 未走索引 |
1. 禁用COUNT 2. 优化查询条件 |
7. 最佳实践总结
-
配置检查清单:
- 确认配置类有
@Configuration - 确认主启动类扫描到配置类
- 分页拦截器是第一个拦截器
- 确认配置类有
-
代码规范建议:
- Controller层统一使用
IPage接收参数 - Service层返回
IPage类型 - 避免在业务代码中手动计算分页偏移量
- Controller层统一使用
-
性能监控指标:
java复制// 开启SQL日志分析 @Bean public PerformanceInterceptor performanceInterceptor() { PerformanceInterceptor interceptor = new PerformanceInterceptor(); interceptor.setFormat(true); return interceptor; }
最后分享一个调试技巧:在application.yml中添加以下配置,可以打印分页插件的详细执行日志:
yaml复制logging:
level:
com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor: DEBUG