掌握分页艺术:MyBatis与MyBatis-Plus实战指南(10年Java亲授)

掌握分页艺术:MyBatis与MyBatis-Plus实战指南(10年Java亲授)

摘要:在高并发、大数据量的现代应用中,分页是每个Java开发者绕不开的核心技能。本文以10年+实战经验为基础,深入浅出地解析MyBatis与MyBatis-Plus的分页实现,配以流程图、代码示例和避坑指南,助你彻底告别"内存溢出"和"全表扫描"噩梦。全文2300+字,零基础也能轻松上手!

一、为什么分页是每个Java开发者的必修课?(分页的价值)

想象一下:你的电商平台有100万商品数据,用户点击"全部商品"时,如果一次性加载所有数据------服务器内存瞬间爆炸,前端页面卡成PPT,数据库CPU直接飙到100% 。这就是不分页的灾难现场。

分页的核心价值在于:

性能优化:避免全表扫描,减少数据库压力(如图1所示,分页查询仅扫描10行而非100万行)

用户体验:用户无需等待" eternity loading",流畅翻页提升转化率

资源节约:降低网络传输量,节省带宽成本

通俗理解 :分页就像图书馆的索引卡------你不需要搬出整栋楼的书,只需按"第3排第5架"精准取书。在Java世界中,offset/limit 或 page/size 就是你的"索引卡"。

二、分页基础:3分钟搞懂核心概念

分页本质是数据切片,主流实现有两种模式:

模式

参数示例

优点

缺点

偏移量模式

offset=0, limit=10

兼容性好,SQL标准

深分页时性能骤降(如offset=10000)

页码模式

page=1, size=10

开发友好,符合用户习惯

需转换为offset/limit

关键公式 :

offset = (page - 1) * size

例如:第3页每页10条 → offset = (3-1)*10 = 20

💡 经验之谈 :10年踩坑总结------永远用页码模式开发(用户说"第5页"而非"跳过40条"),但底层需转为偏移量模式执行SQL。

三、MyBatis分页实战:从RowBounds到PageHelper(附避坑指南)

MyBatis原生支持有限,但通过插件可优雅实现分页。下面分两步拆解:

3.1 原生方案:RowBounds(慎用!)

MyBatis内置RowBounds对象,但仅适用于内存分页 (先查全量再截取),大数据量下是性能毒药!

java

复制代码

// 错误示范:全量查询后内存分页!

List users = sqlSession.selectList("UserMapper.selectUsers", null,

new RowBounds(0, 10)); // offset=0, limit=10

致命问题:

SQL实际执行:SELECT * FROM user(无LIMIT)

数据库返回100万条 → Java内存OOM

仅适用于<1000条的小数据集

3.2 生产级方案:PageHelper插件(MyBatis分页之王)

PageHelper是GitHub 15k+ Star的分页神器,自动重写SQL添加LIMIT。

Step 1:添加依赖(Maven)

xml

复制代码

com.github.pagehelper

pagehelper-spring-boot-starter

1.4.6

Step 2:配置文件(application.yml)

yaml

复制代码

pagehelper:

helper-dialect: mysql # 指定数据库类型

reasonable: true # 启用合理化(页码>总页数时自动查最后一页)

support-methods-arguments: true # 允许传参控制分页

Step 3:代码实现(Controller层)

java

复制代码

@GetMapping("/users")

public PageInfo getUsers(@RequestParam(defaultValue = "1") int pageNum,

@RequestParam(defaultValue = "10") int pageSize) {

// 关键:开启分页(拦截后续第一个查询)

PageHelper.startPage(pageNum, pageSize);

// 执行Mapper查询(无需修改SQL!)

List list = userMapper.selectAll();

// 封装分页对象(含总记录数、总页数等)

return new PageInfo<>(list);

}

Step 4:Mapper XML(无需LIMIT!)

xml

复制代码

避坑指南(10年血泪经验):

必须紧跟第一个查询 :startPage()后必须立即调用select,否则失效

禁止嵌套分页:一个方法内多次分页会导致错乱

深分页优化 :offset>10000时改用WHERE id > last_max_id(游标分页)

四、MyBatis-Plus分页:一行代码解放生产力(效率提升300%)

作为MyBatis的超级增强版,MyBatis-Plus内置分页插件,无需XML,纯Java API,彻底告别SQL改写烦恼。

4.1 为什么选择MyBatis-Plus?

零配置起步:Spring Boot集成仅需1个注解

强类型API :Page对象直接操作,告别字符串参数

自动优化 :深分页时智能切换COUNT查询策略

4.2 三步实现分页(比泡面还简单)

Step 1:添加依赖(Maven)

xml

复制代码

com.baomidou

mybatis-plus-boot-starter

3.5.3.1

Step 2:配置分页插件(Config类)

java

复制代码

@Configuration

public class MybatisPlusConfig {

@Bean

public MybatisPlusInterceptor mybatisPlusInterceptor() {

MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

// 添加分页插件(核心!)

interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

return interceptor;

}

}

Step 3:Mapper继承BaseMapper(无需写SQL!)

java

复制代码

public interface UserMapper extends BaseMapper {

// MyBatis-Plus已内置selectPage方法

}

Step 4:Service层调用(真·一行代码)

java

复制代码

public IPage getUserPage(int pageNum, int pageSize) {

// 创建分页对象:当前页=1,每页10条

Page page = new Page<>(pageNum, pageSize);

// 调用内置分页方法(自动处理COUNT查询和LIMIT)

return userMapper.selectPage(page, null);

}

返回结果解析:

json

复制代码

{

"records": [/* 当前页10条数据 */],

"total": 1000000, // 自动统计的总记录数

"size": 10, // 每页大小

"current": 1, // 当前页码

"pages": 100000 // 总页数

}

4.3 高阶技巧:条件分页 & 性能调优

场景:查询"年龄>25的用户,按注册时间倒序分页"

java

复制代码

// 构建查询条件

QueryWrapper wrapper = new QueryWrapper<>();

wrapper.gt("age", 25).orderByDesc("create_time");

// 分页查询(自动应用条件)

IPage page = userMapper.selectPage(

new Page<>(1, 10),

wrapper

);

性能调优关键点:

关闭COUNT查询 :当总页数不重要时(如无限滚动列表)

java

复制代码

page.optimizeCountSql(false); // 省去COUNT查询,速度提升50%

自定义COUNT查询 :复杂SQL时避免SELECT COUNT(*)慢查询

java

复制代码

wrapper.select("id"); // 只查主键提升COUNT效率

深分页优化 :offset>10000时启用maxMethodLimit

java

复制代码

page.setMaxLimit(10000L); // 超过1万页自动抛异常

五、MyBatis vs MyBatis-Plus分页:终极对比表

维度

MyBatis + PageHelper

MyBatis-Plus

推荐场景

配置复杂度

需额外集成插件

内置分页,仅需1个拦截器

新项目首选MP

代码侵入性

XML需保持无LIMIT

无XML,纯Java调用

追求简洁的团队

深分页性能

依赖PageHelper优化

内置maxMethodLimit保护

大数据量系统

学习成本

需理解拦截器机制

Page对象直觉式操作

新人快速上手

灵活性

支持自定义方言

依赖内置DbType

多数据库混合项目

✅ 10年架构师建议:

老项目迁移:用PageHelper,兼容性强

新项目启动 :无脑选MyBatis-Plus,减少70%分页代码量

亿级数据场景 :结合游标分页(WHERE id > last_id)+ 缓存总记录数

六、分页的终极陷阱:90%开发者踩过的3个大坑

坑1:深分页导致数据库崩溃

现象 :SELECT * FROM order LIMIT 100000, 10 执行超5秒

原理 :MySQL需扫描10万行再丢弃

解法:

游标分页:WHERE id > 100000 ORDER BY id LIMIT 10

提前缓存:Redis存储第10000页的起始ID

坑2:COUNT查询拖垮性能

现象 :总记录数100万时,COUNT(*)耗时3秒

解法:

估算总数:SELECT (SELECT COUNT(*) FROM user) AS total, * FROM user LIMIT 0,10

业务妥协:显示"超过10万条"而非精确值(如淘宝搜索)

坑3:分页与排序冲突

现象 :排序字段非唯一(如按create_time),同一页数据翻页错乱

解法:

排序字段追加唯一ID:ORDER BY create_time DESC, id DESC

结语:分页不是技巧,而是工程思维

分页看似简单,却浓缩了数据库优化、内存管理、用户体验的全栈思维 。作为10年Java老兵,我见过太多团队因分页设计失误导致线上事故------从OOM重启到用户投诉,根源往往是一行缺失的LIMIT。

行动指南:

永远用MyBatis-Plus:新项目拒绝重复造轮子

深分页必做防护 :设置maxLimit或切换游标模式

性能压测先行:模拟10万数据验证分页SQL

最后送大家一句我的座右铭:"能分页的场景不分页,和自行车道开坦克没区别"。掌握本文技术,你不仅能写出健壮的分页代码,更能培养出对系统边界的敬畏之心------这才是高级开发者的真正分水岭。

附:扩展学习资源

MyBatis-Plus分页官方文档

GitHub实战项目:github.com/yourname/pagination-demo(含JMeter压测脚本)

本文由10年Java架构师原创,转载请注明出处。实践出真知,现在就去你的项目里加个分页试试吧!(全文2380字)

相关推荐

美团跑腿兼职全攻略:从注册到接单再到收益详解
365BET体育下载

美团跑腿兼职全攻略:从注册到接单再到收益详解

📅 11-19 👁️ 9965
一夜多少次算正常?华西专家建议不要超这个数
365防伪码查询系统

一夜多少次算正常?华西专家建议不要超这个数

📅 09-30 👁️ 3322
为什么有些女性会被称为“先生”?原来“先生”含义古今略有不同