1. 分页技术:为什么需要它?

在实际业务场景中,数据量大是常态。比如电商平台的商品列表、社交媒体的动态流、企业后台的订单记录,动辄成千上万条数据,直接一次性加载会引发性能问题甚至系统崩溃。分页技术通过对数据进行切片化处理,有效平衡用户体验与系统负载。

以常见的接口请求为例,用户期望的交互模式是:

  1. 客户端传递页码和每页条数
  2. 服务端返回对应页码的数据片段
  3. 同时告知总数以支持页码跳转

这看似简单的要求,后端实现却可能踩到多个技术坑点。MyBatis生态中,PageHelper作为明星级分页插件,是解决这类问题的首选方案,而当遇到特殊场景时,自定义分页则会展示其独特价值。


2. PageHelper完整使用手册

2.1 环境准备(Spring Boot技术栈)

<!-- pom.xml关键依赖 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.6</version>
</dependency>

2.2 配置详解

# application.yml配置参数
pagehelper:
  helper-dialect: mysql          # 指定数据库方言
  reasonable: true               # 页码溢出自动修正(超过最大页返回最后一页)
  support-methods-arguments: true # 支持接口参数直接传递分页参数
  params: count=countSql         # 结果统计模式配置

2.3 基础查询示例

// Service层分页查询实现
public PageInfo<User> listUsers(int pageNum, int pageSize) {
    // 魔法语句触发分页(需紧跟查询语句)
    PageHelper.startPage(pageNum, pageSize);  
    List<User> users = userMapper.selectAll();
    return new PageInfo<>(users);
}

// Controller层接口示例
@GetMapping("/users")
public Result<?> getUserList(
        @RequestParam(defaultValue = "1") Integer pageNum,
        @RequestParam(defaultValue = "10") Integer pageSize) {
    return Result.success(userService.listUsers(pageNum, pageSize));
}

2.4 复杂查询场景

// 多表关联分页示例
public PageInfo<UserOrderDTO> queryUserOrders(Long userId, int page) {
    PageHelper.startPage(page, 20)
              .setOrderBy("create_time DESC");  // 动态排序控制
    List<UserOrderDTO> list = userMapper.selectOrdersWithUserInfo(userId);
    return new PageInfo<>(list);
}

3. 自定义分页深度实践

3.1 何时需要自定义分页?

  • 需要兼容特殊分页格式(如游标分页)
  • 分页逻辑需要深度集成业务规则
  • 处理超高并发场景的优化需求

3.2 实现原理与完整代码

3.2.1 分页参数封装

// 统一分页请求参数基类
public class PageParam {
    private Integer pageNum = 1;    // 当前页码
    private Integer pageSize = 10;  // 每页条数
    private String sortField;       // 排序字段
    private SortOrder sortOrder;    // 排序方向枚举
    
    // 逻辑校验方法
    public void validate() {
        if(pageSize > 100) {
            throw new BusinessException("单页数据量超限");
        }
    }
}

3.2.2 MyBatis拦截器实现

@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 parameter = invocation.getArgs()[1];
        if (parameter instanceof PageParam) {
            PageParam pageParam = (PageParam)parameter;
            // 执行分页SQL重写逻辑
            rewriteQuerySql(invocation, pageParam);
        }
        return invocation.proceed();
    }

    // SQL改写核心方法(示例简写)
    private void rewriteQuerySql(Invocation invocation, 
                                PageParam param) {
        BoundSql boundSql = ((MappedStatement)invocation.getArgs()[0])
                              .getBoundSql(param);
        String newSql = boundSql.getSql() 
                      + " LIMIT " + param.getOffset() 
                      + "," + param.getPageSize();
        // 通过反射修改SQL语句
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql, newSql);
    }
}

3.3 分页结果封装

// 统一分页响应结构
public class PageResult<T> {
    private List<T> records;        // 当前页数据
    private Long total;              // 总记录数
    private Integer currentPage;     // 当前页码
    private Boolean hasNext;         // 是否存在下一页
    
    // 构造方法封装计算逻辑
    public PageResult(List<T> records, Long total, PageParam param) {
        this.records = records;
        this.total = total;
        this.currentPage = param.getPageNum();
        this.hasNext = total > param.getPageNum() * param.getPageSize();
    }
}

4. 核心技术对比分析

4.1 PageHelper核心优势

  • 零学习成本:API简单直观,仅需一行代码即可实现分页
  • 智能方言适配:支持12种主流数据库自动识别
  • 丰富扩展能力:支持排序控制、count优化等高级特性
  • 开源社区活跃:GitHub标星超过5k,问题响应及时

4.2 自定义分页存在价值

  • 性能极致优化:可针对特定SQL做索引优化
  • 灵活游标支持:实现基于最后ID的分页模式
  • 安全管控增强:添加业务层数据权限过滤
  • 协议格式定制:适配移动端特殊分页格式要求

5. 生产注意事项

5.1 PageHelper使用陷阱

  • 线程污染风险:分页设置必须与当前请求严格绑定
  • 嵌套查询问题:子查询中的分页可能被意外触发
  • COUNT性能:复杂关联查询建议手动优化count语句

5.2 自定义分页关键点

  • 事务边界控制:分页查询与count查询保持原子性
  • SQL注入防护:排序字段必须白名单校验
  • 偏移量计算优化:大数据量分页采用索引覆盖方案

6. 典型应用场景指南

6.1 PageHelper理想场景

  • 后台管理系统表格分页
  • 移动端瀑布流分页(需配合回收机制)
  • 常规列表接口开发

6.2 自定义分页必选场景

  • 千万级数据深度分页优化
  • 需要结合Redis缓存的分页逻辑
  • 基于地理位置的多维度排序分页
  • 需要数据权限过滤的分页查询

7. 技术选型决策树

通过以下流程图帮助决策:

  1. 是否要求快速实现 → 选PageHelper
  2. 是否需要深度性能优化 → 选自定义
  3. 是否有特殊协议要求 → 选自定义+二次封装

8. 总结与展望

分页虽小,却承载着系统性能与用户体验的微妙平衡。PageHelper作为开箱即用的解决方案,适合80%的常规场景;而自定义分页则为复杂需求提供了灵活扩展的可能。随着分布式数据库的普及,未来的分页技术可能更深度集成到数据库访问层,例如TiDB的Region分片机制,将分页逻辑下推到存储引擎层,这将是值得持续关注的技术趋势。