1. SpringBoot整合Mybatis-PageHelper基础配置在SpringBoot项目中集成Mybatis-PageHelper分页插件首先需要在pom.xml中添加starter依赖。这里推荐使用pagehelper-spring-boot-starter它会自动完成大部分配置工作dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.6/version /dependency对于SpringBoot 2.x及以上版本建议使用1.4.x版本的starter。这个starter会自动配置以下内容自动检测使用的数据库类型自动注册PageInterceptor拦截器自动配置合理的默认参数在application.yml中可以进行基础配置pagehelper: helperDialect: mysql reasonable: true supportMethodsArguments: true params: countcountSql提示helperDialect参数虽然可以自动检测但在多数据源场景下显式指定更可靠。reasonable参数设置为true时当页码超出范围会自动修正到合理范围。2. 核心分页功能实现与原理2.1 基础分页查询实现在Service层实现分页查询的典型代码如下public PageInfoUser findUsers(int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); ListUser users userMapper.selectAll(); return new PageInfo(users); }这段代码的工作原理是PageHelper.startPage()方法将分页参数存入ThreadLocal后续第一个MyBatis查询方法会被拦截器检测到拦截器根据分页参数改写SQL添加LIMIT子句同时自动执行COUNT查询获取总记录数最终返回的List实际是Page对象包含分页信息2.2 PageInfo对象详解PageInfo是PageHelper提供的分页结果包装类包含丰富的分页信息// 获取PageInfo后可以访问以下属性 pageInfo.getList(); // 结果集 pageInfo.getTotal(); // 总记录数 pageInfo.getPages(); // 总页数 pageInfo.getPageNum(); // 当前页码 pageInfo.getPageSize(); // 每页数量 pageInfo.isFirstPage(); // 是否第一页 pageInfo.isLastPage(); // 是否最后一页2.3 分页原理深度解析PageHelper的核心拦截器是PageInterceptor它的工作流程如下拦截Executor.query()方法检查ThreadLocal中是否存在分页参数存在参数时使用JSqlParser解析原始SQL生成COUNT查询SQL并执行获取总数修改原始SQL添加数据库特定的分页语句执行分页查询并封装结果到Page对象对于MySQL数据库PageHelper会将SQL从SELECT * FROM user WHERE age 20改写成SELECT * FROM user WHERE age 20 LIMIT 0,103. 高级功能与最佳实践3.1 多数据源分页配置在多数据源环境下需要特殊配置确保分页正确pagehelper: autoRuntimeDialect: true closeConn: false关键参数说明autoRuntimeDialect运行时自动检测数据源类型closeConn设置为false防止连接被提前关闭3.2 复杂查询分页处理对于包含JOIN或子查询的复杂SQLPageHelper可能无法自动优化COUNT查询。此时可以自定义COUNT查询Select({script, SELECT count(*) FROM user u WHERE u.age #{age}, /script}) Long countUsers(Param(age) int age); Select({script, SELECT * FROM user u WHERE u.age #{age}, /script}) ListUser selectUsers(Param(age) int age, RowBounds rowBounds);使用PageHelper提供的ISelect接口PageUser page PageHelper.startPage(1, 10) .doSelectPage(() - userMapper.selectComplexQuery(params));3.3 性能优化建议对于百万级数据分页推荐使用延迟关联优化技巧SELECT * FROM user INNER JOIN ( SELECT id FROM user ORDER BY create_time DESC LIMIT 100000,10 ) AS tmp USING(id)启用异步COUNT查询提升性能pagehelper: asyncCount: true asyncCountParallelism: 4对于不需要总数的情况可以关闭COUNT查询PageHelper.startPage(1, 10, false); // 第三个参数设为false4. 常见问题排查与解决方案4.1 分页失效问题排查如果发现分页没有生效可以按以下步骤排查检查是否在查询方法前调用PageHelper.startPage()确保startPage()和查询语句之间没有其他查询检查是否有多个PageInterceptor被注册开启debug模式查看拦截器日志pagehelper: debug: true4.2 总数统计不准确当发现分页的总数统计不准确时检查SQL是否包含GROUP BY需要手动优化COUNT查询对于复杂JOIN查询考虑使用SelectProvider自定义COUNT查询检查是否有WHERE条件使用了不稳定函数如NOW()4.3 内存溢出问题处理大数据量分页时可能遇到OOM解决方案使用流式查询代替分页Select(SELECT * FROM large_table) Options(resultSetType ResultSetType.FORWARD_ONLY, fetchSize 1000) void streamLargeData(ResultHandlerUser handler);采用游标分页模式// 第一页 ListUser firstPage userMapper.selectAfterId(0, 10); // 获取最后一行的ID Long lastId firstPage.get(firstPage.size()-1).getId(); // 下一页 ListUser nextPage userMapper.selectAfterId(lastId, 10);5. 实战案例前后端分离分页实现5.1 后端接口实现典型的RESTful分页接口实现GetMapping(/users) public ResultPageInfoUser getUserPage( RequestParam(defaultValue 1) int pageNum, RequestParam(defaultValue 10) int pageSize) { PageHelper.startPage(pageNum, pageSize); ListUser users userService.selectAll(); PageInfoUser pageInfo new PageInfo(users); return Result.success(pageInfo); }5.2 前端分页参数处理前端如Vue调用分页接口的示例async loadData(page 1) { const res await axios.get(/users, { params: { pageNum: page, pageSize: 10 } }); this.users res.data.list; this.pageInfo res.data; }5.3 统一分页响应格式建议的统一响应格式{ code: 200, message: success, data: { list: [...], pageNum: 1, pageSize: 10, total: 100, pages: 10 } }6. 扩展功能与自定义开发6.1 自定义分页方言对于特殊数据库可以实现自定义方言public class CustomDialect extends AbstractHelperDialect { Override public String getPageSql(String sql, Page page) { // 实现自定义分页逻辑 return sql LIMIT page.getStartRow() , page.getPageSize(); } }然后在配置中指定pagehelper: dialectAlias: customcom.example.CustomDialect6.2 分页拦截器扩展可以扩展PageInterceptor添加自定义逻辑public class CustomPageInterceptor extends PageInterceptor { Override public Object intercept(Invocation invocation) throws Throwable { // 前置处理 Object result super.intercept(invocation); // 后置处理 return result; } }6.3 与MyBatis-Plus共存当同时使用MyBatis-Plus和PageHelper时需要注意确保PageInterceptor在MyBatis-Plus拦截器之前执行不要在同一个方法中混用两种分页方式可以在配置中明确指定拦截器顺序mybatis: configuration: interceptor-order: pageInterceptor,mybatisPlusInterceptor在实际项目开发中PageHelper的分页功能已经能够满足绝大多数场景需求。关键是要理解其工作原理这样才能在遇到特殊需求时能够灵活应对。我个人的经验是对于常规分页直接使用PageHelper.startPage()最简单对于复杂分页场景可以考虑手动实现COUNT查询或使用游标分页等优化方案。