一、 实例简介:为什么要进行数据分页?

在企业级应用开发中,当数据库中存储了海量数据(例如 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.vuemethods 中,当后端成功响应后,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: 5rows 数组的 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。在这里,系统动态获取了前端传来的 pageNumpageSize 参数,并最终调用 MyBatis 插件的 PageHelper.startPage() 真正开启分页拦截:

(图13:PageUtils.java 中真正执行分页拦截的底层代码)

查询出数据后,BaseControllergetDataTable() 方法会将数据放入 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 限制)

原理剖析:

  1. 查总数:第一句生成 SELECT count(0) FROM my_student,用于查询表的总行数(对应日志中的 Total: 5)。

  2. 查分页数据:第二句在原查询语句末尾自动拼接了 LIMIT ?,对查询数据做了物理行数限制。

五、总结

通过对若依 CSMD 架构的前后链路分析,我们看到了一次完整的分页流转:前端传参 -> 控制器解析开启 PageHelper -> MyBatis 拦截改写 SQL -> 数据库执行 LIMIT 物理分页 -> 后端统一包装总条数 -> 前端渲染页码。这种设计极大地提升了系统的查询性能和稳定性。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐