1. 分页需求与PageHelper方案选型
在Web应用开发中,分页处理是最基础也最频繁遇到的需求场景。以电商平台为例,当商品列表达到上万条时,直接返回全量数据不仅消耗带宽,还会导致前端渲染卡顿。传统分页需要手动计算limit/offset参数,而MyBatis生态中的PageHelper插件通过拦截器机制,用极简的API实现了物理分页的自动化处理。
为什么选择PageHelper?相比手写分页SQL,它有三大不可替代的优势:
- 与MyBatis无缝集成,无需修改Mapper文件
- 支持多种数据库方言(MySQL、Oracle等)
- 提供PageInfo包装类,直接获取总页数、当前页等关键参数
2. 两种依赖引入方式详解
2.1 标准Maven依赖(推荐)
在pom.xml中添加以下配置:
xml复制<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.3</version>
</dependency>
注意:使用starter包会自动配置拦截器,无需手动添加@Bean配置。版本选择上,1.4.x系列对SpringBoot 2.x兼容性最佳。
2.2 传统Spring项目适配方案
对于非SpringBoot项目,需要组合引入核心包和适配器:
xml复制<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.2</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring</artifactId>
<version>1.0.0</version>
</dependency>
配置类需手动声明拦截器:
java复制@Bean
public PageInterceptor pageInterceptor() {
return new PageInterceptor();
}
3. 核心配置参数调优
在application.yml中建议配置以下参数:
yaml复制pagehelper:
helperDialect: mysql
reasonable: true
supportMethodsArguments: true
params: count=countSql
关键参数说明:
helperDialect:明确指定数据库类型,避免自动检测开销reasonable:启用合理化分页(页码超出范围时返回末页)params:将count查询改为countSql优化性能
4. 分页实战代码示范
4.1 基础分页查询
java复制public PageInfo<User> listUsers(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectAll();
return new PageInfo<>(users);
}
4.2 复杂多表分页
java复制public PageInfo<OrderVO> listOrders(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize)
.setOrderBy("o.create_time DESC");
List<OrderVO> orders = orderMapper.selectWithUserInfo();
return new PageInfo<>(orders);
}
经验:多表关联查询时,务必在startPage()后立即执行Mapper方法,避免中间插入其他SQL导致分页失效。
5. 高级功能与性能优化
5.1 分页缓存穿透防护
java复制public PageInfo<Product> searchProducts(ProductQuery query) {
// 限制最大分页深度
if(query.getPageNum() > 100) {
query.setPageNum(100);
}
PageHelper.startPage(query.getPageNum(), 20);
return new PageInfo<>(productMapper.search(query));
}
5.2 Count查询优化
对于百万级数据表,可通过重写count语句提升性能:
java复制@SelectProvider(type = ProductSqlBuilder.class, method = "search")
@Options(useCache = false, timeout = 5000)
List<Product> search(ProductQuery query);
class ProductSqlBuilder {
public String search(ProductQuery query) {
SQL sql = new SQL()
.SELECT("*")
.FROM("product")
.WHERE("status = 1");
if(StringUtils.isNotBlank(query.getKeyword())) {
sql.WHERE("name LIKE CONCAT('%',#{keyword},'%')");
}
return sql.toString();
}
public String searchCount(ProductQuery query) {
return search(query).replace("*", "COUNT(1)");
}
}
6. 常见问题排查指南
6.1 分页失效场景
-
线程污染:分页参数存储在ThreadLocal中,异步场景需手动清理
java复制try { PageHelper.startPage(1, 10); // 查询逻辑 } finally { PageHelper.clearPage(); } -
SQL执行顺序错误:PageHelper只对紧随其后的第一个查询生效
6.2 性能问题排查
- 现象:分页查询变慢
- 检查方向:
- 是否缺少
count=countSql配置 - 是否在分页查询中使用了
order by但无索引 - 是否对大偏移量分页(如limit 100000,20)
- 是否缺少
对于深度分页建议改用"上一页最大ID"方案:
sql复制SELECT * FROM table WHERE id > #{lastMaxId} ORDER BY id LIMIT 20
7. 扩展功能开发
7.1 自定义PageInfo扩展
java复制public class CustomPageInfo<T> extends PageInfo<T> {
private String searchId;
public CustomPageInfo(List<T> list) {
super(list);
}
// 添加自定义分页参数
}
7.2 多数据源分页支持
在配置类中指定分页插件使用的数据源:
java复制@Bean
public PageInterceptor pageInterceptor(@Qualifier("mainDataSource") DataSource dataSource) {
PageInterceptor interceptor = new PageInterceptor();
Properties props = new Properties();
props.setProperty("helperDialect", "mysql");
interceptor.setProperties(props);
interceptor.setDataSource(dataSource); // 关键注入点
return interceptor;
}
实际项目中发现,当使用PageHelper的startPage()后立即调用存储过程时,会导致分页参数意外传播到存储过程内部。这时需要在存储过程调用前显式执行PageHelper.clearPage()