一、 实例简介:为什么要进行数据分页?
在企业级应用开发中,当数据库中存储了海量数据(例如 10000 个用户信息)时,如果我们一次性将所有数据从后端读取并展示到前端,会面临巨大的性能灾难。如果不分页,系统会导致前端浏览器内存溢出、网络带宽被大量占用、后端数据库全表扫描性能极差。
1.在课堂上,我们分析了系统自带的“操作日志”页面的分页过程:
进入方式:系统管理 -> 日志管理 -> 操作日志
(图1:系统自带的操作日志页面展示)
2.为了加深对CSMD架构底层数据流转的理解,本文将不使用系统自带的日志模块,而是以我自定义开发的“系统工具 -> 学生信息”模块为例,深度剖析若依(RuoYi-Vue)框架是如何优雅地实现前后端数据分页的。
(图2:我自定义的“学生信息”页面,红框标记了左侧菜单与右下角的分页组件)
二、 源码分析:前端代码与网络请求机制
1. 前端页面组件 (index.vue)
①首先,我们找到学生信息页面的前端源码文件路径:
②打开该文件,可以看到页面底部使用了<pagination>组件。该组件绑定的:total="total"初始值为 0,且通过@pagination="getList"绑定了翻页事件。当用户在界面上点击“第几页”或切换“每页条数”时,会立即触发该函数:
(图3:前端<pagination>分页组件代码及属性绑定)
2. 函数调用链:参数获取与接口请求
③在页面上方,用户可以通过日期选择框和搜索按钮来触发查询:
(图4:前端搜索条件 - 生日选择框)
(图5:前端搜索条件 - 搜索触发按钮)
④数据的获取遵循调用链:getList -> listStudent -> request[axios]。首先,在api/system/student.js中,通过引入axios封装的网络通信工具,定义了向后端发起/system/student/list请求的方法:
(图6:student.js中定义的访问后端 URL 与 GET 请求方式)
⑤回到index.vue的methods中,当后端成功响应后,getList()函数会将返回的response.total赋值给this.total:
(图7:getList方法的完整结构)
(图8:重点逻辑放大——只有当 total 被赋值后,页面才会通过 pagination 组件显示数据)
3. F12 开发者工具抓包验证
⑥按下 F12 打开开发者工具,切换到 Network(网络)面板,过滤Fetch/XHR交互请求。 首先查看Headers(标头),可以看到前端发往后端的 URL 中携带了两个核心参数:pageNum=1(第几页)和pageSize=10(每页条数):
(图9:开发者工具查看前端访问后端的请求参数信息)
⑦切换到Preview(预览)面板,可以看到后端返回了包含total: 5和rows数组的 JSON 数据。返回的数据条数精准匹配了请求的分页限制:
(图10:开发者工具查看前端接收到后端的回应信息)
三、 源码分析:后端代码与分页核心封装
前端发送了分页参数,后端是如何接收并处理的呢?
1. 控制器入口 (MyStudentController.java)
进入MyStudentController.java,找到响应前端 HTTP 请求的@GetMapping("/list")方法。实现分页只需要两行核心代码:startPage();(开启分页)和return getDataTable(list);(包装返回结果)。
(图11:后端学生信息 Controller 的分页实现逻辑)
2. 底层包装逻辑透视
这两个方法来自于若依的基类BaseController.java:
(图12:BaseController.java中的startPage()方法)
startPage()实际上调用了工具类PageUtils.java。在这里,系统动态获取了前端传来的pageNum和pageSize参数,并最终调用 MyBatis 插件的PageHelper.startPage()真正开启分页拦截:
(图13:PageUtils.java中真正执行分页拦截的底层代码)
查询出数据后,BaseController的getDataTable()方法会将数据放入TableDataInfo中,并通过new PageInfo(list).getTotal()自动计算表数据的总行数,最终返回给前端:
(图14:BaseController.java中的getDataTable()数据包装逻辑)
四、 分页实现原理分析:MyBatis 与 SQL 日志剖析
我们在 Mapper 的 XML 文件中写的是普通的SELECT语句,并没有手动写pageNum和pageSize,那它是如何截取数据的呢?这归功于 MyBatis 的PageHelper插件拦截机制。
观察 IDEA 控制台打印出的 Log 日志,我们会发现原本的一句SQL,被 MyBatis拦截并动态改写成了两句SQL:
(图15:MyBatis 拦截后的控制台输出,包含 count 统计与 LIMIT 限制)
原理剖析:
查总数:第一句生成
SELECT count(0) FROM my_student,用于查询表的总行数(对应日志中的 Total: 5)。查分页数据:第二句在原查询语句末尾自动拼接了
LIMIT ?,对查询数据做了物理行数限制。