MyBatis物理分页插件配置:从入门到性能优化实战指南
📚 目录导读
- 为什么必须用物理分页?逻辑分页的陷阱
- 主流物理分页插件选型对比
- PageHelper插件配置全解(含Spring Boot)
- MyBatis-Plus分页插件配置详解
- 自定义分页拦截器实现物理分页
- 常见配置陷阱与性能调优
- Q&A 高频问题解答

为什么必须用物理分页?逻辑分页的陷阱
在MyBatis开发中,分页是最常见的需求,许多初学者会使用List.subList()或内存分页,但这会引发严重性能问题:
- 逻辑分页:一次性查询所有数据到内存,再截取部分,当数据量超过10万行时,内存可能瞬间溢出,数据库连接被长时间占用。
- 物理分页:在SQL层面添加
LIMIT或ROWNUM,只查询当前页数据,内存和数据库负载可控。
性能对比:在100万条数据场景下,物理分页响应时间仅需毫秒级,而逻辑分页可能超过10秒,且内存消耗高达数百MB。
物理分页是生产环境的必要选择,而配置分页插件是实现物理分页最优雅的方式。
主流物理分页插件选型对比
目前Java生态中,MyBatis物理分页插件主要有以下几种:
| 插件名称 | 数据库支持 | 特点 | 适用场景 |
|---|---|---|---|
| PageHelper | MySQL、Oracle、PostgreSQL等12种 | 功能最全,自动方言识别 | 通用型项目,需复杂分页逻辑 |
| MyBatis-Plus分页插件 | MySQL、Oracle、SQL Server等 | 与MP深度整合,API简洁 | 使用MyBatis-Plus的项目 |
| MyBatis-Pageable | 主流数据库 | 轻量级,无冗余功能 | 微服务或轻量级项目 |
| 自定义拦截器 | 按需实现 | 灵活可控,无依赖 | 对分页有特殊定制需求 |
推荐:90%的场景选择PageHelper或MyBatis-Plus插件即可满足。
PageHelper插件配置全解(含Spring Boot)
1 Spring Boot整合(推荐方式)
<!-- pom.xml -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
</dependency>
在application.yml中配置:
pagehelper: helper-dialect: mysql # 自动识别(也可指定:mysql/oracle/postgresql) reasonable: true # 合理化分页,页码超出范围时自动修正 support-methods-arguments: true # 支持通过Mapper参数传递Page对象 params: count=countSql # 自动生成COUNT查询 auto-runtime-dialect: true # 自动适配不同数据库(多数据源时开启)
2 传统XML配置(适用于非Spring Boot)
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
<property name="reasonable" value="true"/>
<property name="supportMethodsArguments" value="true"/>
</plugin>
</plugins>
3 代码使用示例
// Service层
public PageInfo<User> getUsersByPage(int pageNum, int pageSize) {
// 1. 设置分页参数(必须在查询之前)
PageHelper.startPage(pageNum, pageSize);
// 2. 执行查询(自动拦截并添加LIMIT)
List<User> users = userMapper.selectAll();
// 3. 封装分页结果(包含总记录数、页码等)
return new PageInfo<>(users);
}
// Mapper接口无需修改,直接返回List
List<User> selectAll();
关键原理:PageHelper使用MyBatis的拦截器机制,在SQL执行前动态拼接LIMIT x OFFSET y,并自动生成COUNT查询。
MyBatis-Plus分页插件配置详解
1 配置步骤
<!-- 引入MP依赖后,需单独配置分页插件 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
Java配置类:
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页拦截器,指定数据库类型
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInterceptor.setOverflow(true); // 溢出后自动跳转到首页
paginationInterceptor.setMaxLimit(1000L); // 单页最大条数限制
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}
}
2 使用方式
// 使用MP的Page对象
public Page<User> getUserPage(int current, int size) {
Page<User> page = new Page<>(current, size);
// 条件构造器(可选)
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("status", 1);
// 分页查询,结果自动封装到page对象
return userMapper.selectPage(page, wrapper);
}
注意事项:MP的分页插件与PageHelper不兼容,两者只能选其一。
自定义分页拦截器实现物理分页
当插件不满足需求时(如特殊分页逻辑、多租户分页),可自定义拦截器。
@Intercepts({
@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class CustomPageInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
// 判断是否使用分页参数
if (parameter instanceof PageParam) {
PageParam pageParam = (PageParam) parameter;
BoundSql boundSql = ms.getBoundSql(parameter);
String originalSql = boundSql.getSql();
// 拼接LIMIT语句
String pageSql = originalSql + " LIMIT " + pageParam.getOffset()
+ "," + pageParam.getPageSize();
// 替换SQL
BoundSql newBoundSql = new BoundSql(
ms.getConfiguration(), pageSql,
boundSql.getParameterMappings(), parameter);
args[0] = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(),
new SqlSource() {
@Override
public BoundSql getBoundSql(Object parameterObject) {
return newBoundSql;
}
}, ms.getSqlCommandType()).build();
}
return invocation.proceed();
}
}
优点:可根据业务自定义COUNT查询优化、动态条件拼接。缺点:需要处理数据库方言差异。
常见配置陷阱与性能调优
1 配置陷阱
-
多次调用PageHelper.startPage:每次查询前只调用一次,避免嵌套分页。
-
分页参数未传递到Mapper:确保
support-methods-arguments=true,或通过Page对象传递。 -
COUNT查询性能瓶颈:默认生成
SELECT COUNT(1),若表数据量大,可手动优化:- 添加索引覆盖
WHERE条件 - 使用
countSql参数指定自定义COUNT查询
- 添加索引覆盖
-
多数据源下方言识别失败:设置
auto-runtime-dialect=true,或手动指定helper-dialect。
2 性能调优建议
- 设置合理的pageSize上限:在插件中通过
maxLimit限制,防止恶意请求。 - 缓存COUNT结果:若数据更新不频繁,可用Redis缓存总记录数,避免重复查询。
- 分页与排序结合:确保排序字段有索引,避免文件排序。
- 避免大偏移量分页:深度分页(如第1000页)应改用游标或子查询优化。
Q&A 高频问题解答
Q1:PageHelper和MyBatis-Plus分页插件可以共存吗? A:不推荐,两者都基于拦截器,同时使用可能产生冲突,导致分页失效或SQL混乱,选用其一即可。
Q2:分页查询返回的总记录数不准确怎么办?
A:检查是否有多表联查且包含GROUP BY,此时COUNT可能不准确,可手动编写COUNT查询,通过PageHelper的countSql参数指定。
Q3:如何实现不查询总记录数的快速分页(仅返回当前页)?
A:PageHelper支持:PageHelper.startPage(pageNum, pageSize, false),第三个参数为false时跳过COUNT查询。
Q4:分页插件在Oracle数据库下报错,如何处理?
A:Oracle使用ROWNUM方式分页,确认helper-dialect设置为oracle,若使用MyBatis-Plus,需设置DbType.ORACLE。
Q5:自定义拦截器如何处理不同数据库方言?
A:通过DatabaseIdProvider获取当前数据库类型,动态拼接不同的分页SQL(如MySQL用LIMIT,Oracle用ROWNUM嵌套)。
Q6:分页参数在Controller层如何传递?
A:推荐使用Spring Boot的Pageable对象自动绑定:@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size,再通过PageHelper封装。
Q7:分页插件是否支持嵌套查询的分页? A:不支持,嵌套查询(如子查询中的分页)需要手动修改SQL,因为插件只拦截最外层查询。
物理分页插件配置是MyBatis性能优化的基础环节,选择PageHelper还是MyBatis-Plus插件,取决于项目技术栈和偏好,无论使用哪种,关键配置包括:数据库方言准确设置、合理性分页开启、COUNT查询优化,对于高并发场景,建议结合缓存和索引优化,并严格限制单页最大数量。
配置完成后,记得通过PageInfo或Page对象获取分页元数据,避免手动计算偏移量,如果遇到分页失效或性能问题,优先检查拦截器加载顺序和SQL日志,这能快速定位问题根源。