MyBatis-Plus分页查询实战与优化指南

📅 2026/7/28 9:28:44
MyBatis-Plus分页查询实战与优化指南
1. MyBatis-Plus分页查询实战指南作为Java开发者你一定遇到过需要处理大量数据展示的场景。传统的分页实现往往需要编写大量重复代码而MyBatis-Plus的分页插件让这一切变得异常简单。我在实际项目中多次使用MyBatis-Plus的分页功能特别是在处理百万级数据表时它的稳定性和便捷性给我留下了深刻印象。MyBatis-Plus的分页功能基于拦截器实现通过简单的配置就能为你的查询自动添加分页逻辑。不同于手动编写LIMIT语句这种方式保持了代码的整洁性同时提供了丰富的定制选项。无论是简单的单表查询还是复杂的多表关联都能轻松应对。2. 环境准备与基础配置2.1 依赖引入首先确保你的项目已经正确引入了MyBatis-Plus的依赖。对于Maven项目在pom.xml中添加dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency注意版本号请根据实际情况选择建议使用3.4.0以上的稳定版本以获得最佳的分页体验。2.2 分页插件配置在Spring Boot应用中你需要配置分页拦截器。创建一个配置类Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }这里我们指定了数据库类型为MySQLMyBatis-Plus会根据不同的数据库类型生成合适的分页SQL。支持的数据库类型包括MYSQL、ORACLE、DB2、H2、HSQL、SQLITE、POSTGRE_SQL、SQLSERVER等。3. 基础分页查询实现3.1 基本使用方式最简单的分页查询可以通过Page对象实现// 创建分页对象参数分别是当前页和每页大小 PageUser page new Page(1, 10); // 执行分页查询 PageUser result userMapper.selectPage(page, null); // 获取分页数据 ListUser records result.getRecords(); // 当前页数据 long total result.getTotal(); // 总记录数 long pages result.getPages(); // 总页数这种基础用法适合简单的单表查询场景。Page对象的第二个参数是查询条件Wrapper如果为null则表示无条件查询。3.2 带条件的分页查询实际项目中我们通常需要根据条件筛选数据// 创建查询条件 LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.like(User::getName, 张) .gt(User::getAge, 18); // 执行分页查询 PageUser page new Page(1, 10); PageUser result userMapper.selectPage(page, wrapper);这里使用了LambdaQueryWrapper来构建类型安全的查询条件避免了硬编码字段名。MyBatis-Plus会自动将条件组合到SQL中并添加分页限制。4. 高级分页技巧4.1 自定义分页SQL对于复杂的多表关联查询你可能需要自定义SQL语句。MyBatis-Plus同样支持!-- Mapper XML中定义 -- select idselectUserPage resultTypeUser SELECT u.*, d.dept_name FROM user u LEFT JOIN department d ON u.dept_id d.id ${ew.customSqlSegment} /select对应的Mapper接口和调用方式// Mapper接口 IPageUser selectUserPage(IPageUser page, Param(Constants.WRAPPER) WrapperUser wrapper); // 调用方式 PageUser page new Page(1, 10); LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.eq(User::getStatus, 1); PageUser result userMapper.selectUserPage(page, wrapper);提示${ew.customSqlSegment}是MyBatis-Plus的特殊语法会自动替换为Wrapper生成的WHERE条件。4.2 分页优化技巧当处理大数据量时分页性能尤为重要。以下是几个优化建议避免使用count查询对于特别大的表count操作可能很耗时。如果不需要总记录数可以PageUser page new Page(1, 10, false); // 第三个参数设为false表示不执行count查询使用优化后的分页SQL对于MySQL可以使用延迟关联优化深分页SELECT * FROM user INNER JOIN ( SELECT id FROM user WHERE status 1 ORDER BY create_time DESC LIMIT 100000, 10 ) AS tmp USING(id)合理设置分页大小单页数据量过大(如500条以上)会增加内存和网络负担。建议根据实际需求设置合理的pageSize。5. 常见问题与解决方案5.1 分页失效问题现象配置了分页插件但分页不生效返回了全部数据。可能原因及解决方案分页拦截器未正确配置 - 检查配置类是否被Spring加载使用了错误的Page类 - 确保使用com.baomidou.mybatisplus.extension.plugins.pagination.PageSQL语句不规范 - 自定义SQL中缺少${ew.customSqlSegment}5.2 性能问题现象分页查询响应慢特别是后面几页。解决方案为排序字段和条件字段添加索引使用上一页/下一页导航代替直接跳转到很远的页码考虑使用基于游标的分页方式适合无限滚动场景5.3 多表关联分页问题多表join时分页结果不准确。解决方案确保主表字段是分页基准使用子查询先分页再关联select idselectUserWithDept resultTypeUserDTO SELECT u.*, d.dept_name FROM (SELECT * FROM user WHERE status 1 LIMIT #{offset}, #{size}) u LEFT JOIN department d ON u.dept_id d.id /select6. 实际项目中的经验分享6.1 统一分页响应格式在实际项目中我通常会封装统一的分页响应格式public class PageResultT { private long current; private long size; private long total; private long pages; private ListT records; // 从IPage转换 public static T PageResultT from(IPageT page) { PageResultT result new PageResult(); result.setCurrent(page.getCurrent()); result.setSize(page.getSize()); result.setTotal(page.getTotal()); result.setPages(page.getPages()); result.setRecords(page.getRecords()); return result; } // getters and setters }这样前端可以统一处理分页数据接口也更加规范。6.2 动态分页参数处理对于接收前端分页参数的Controller我通常这样处理GetMapping(/users) public PageResultUser listUsers( RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, UserQuery query) { PageUser page new Page(pageNum, pageSize); LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); // 动态构建查询条件 if (StringUtils.isNotBlank(query.getName())) { wrapper.like(User::getName, query.getName()); } if (query.getMinAge() ! null) { wrapper.ge(User::getAge, query.getMinAge()); } return PageResult.from(userMapper.selectPage(page, wrapper)); }6.3 分页与缓存结合对于访问频繁但变化不频繁的数据可以考虑将分页结果缓存Cacheable(value userPage, key #pageNum - #pageSize - #query.hashCode()) public PageResultUser getCachedUserPage(Integer pageNum, Integer pageSize, UserQuery query) { // 分页查询逻辑 }但要注意缓存的时效性和内存占用特别是对于大数据量分页。7. MyBatis-Plus分页原理剖析了解分页的实现原理有助于更好地使用和调试。MyBatis-Plus的分页功能主要通过以下组件实现PaginationInnerInterceptor核心拦截器在执行SQL前进行拦截和改写JsqlParserSQL解析器用于分析原始SQL语句DbType数据库类型识别用于生成不同数据库的分页语句拦截器的工作流程检测方法参数中是否存在IPage对象执行count查询获取总数如果配置需要改写原始SQL添加分页语句MySQL: LIMIT offset, sizeOracle: 使用ROWNUMPostgreSQL: LIMIT size OFFSET offset执行分页SQL并封装结果8. 版本兼容性与升级注意事项随着MyBatis-Plus版本的更新分页功能也有了一些变化3.4.x → 3.5.x分页拦截器从PaginationInterceptor变更为PaginationInnerInterceptor需要通过MybatisPlusInterceptor添加count查询优化新版对count查询进行了优化性能有所提升JSqlParser升级注意与项目中其他依赖的兼容性在升级版本时建议仔细阅读官方升级指南先在测试环境验证分页功能检查自定义SQL的分页行为是否变化我在实际项目中从3.4.3升级到3.5.3时发现一些复杂的自定义SQL分页行为有变化需要调整SQL语句才能正常工作。9. 替代方案与比较虽然MyBatis-Plus的分页很方便但在某些场景下可能需要考虑其他方案物理分页 vs 逻辑分页MyBatis-Plus属于物理分页数据库层面逻辑分页如Java内存中分页适合小数据量其他分页插件PageHelper另一种流行的MyBatis分页插件Spring Data与JPA配合使用的分页抽象特殊分页需求游标分页基于上一页最后一条记录的位置键集分页使用有序唯一键进行高效分页选择方案时要考虑数据量、性能要求和开发效率的平衡。对于大多数CRUD应用MyBatis-Plus的分页已经足够优秀。10. 最佳实践总结经过多个项目的实践我总结了以下MyBatis-Plus分页的最佳实践合理设置分页大小根据业务需求和性能考虑通常10-100条/页比较合适避免不必要count查询当总记录数不重要时关闭count查询提升性能索引优化确保分页查询的条件和排序字段有合适索引统一分页响应封装统一的分页响应对象便于前后端协作监控慢查询对分页SQL进行监控及时发现性能问题考虑数据增长设计时要考虑数据量增长后的分页性能最后分享一个我在处理千万级用户表时的经验当用户需要查询非常靠后的页码时如第1000页传统的LIMIT offset,size方式性能会很差。这种情况下我改用记住上一页最后一条记录的ID的方式使用WHERE id last_id LIMIT size来实现高效分页效果显著提升。