大家好,这依旧是一篇个人笔记,这次使用了AI工具辅助文章的生成,效率更高,不喜勿喷

首先给出思维导图

一、整体架构概览

在JavaWeb项目开发中,分页查询是一个非常常见的功能需求。本文将以"员工列表查询"为例,详细介绍如何基于 PageHelper 分页插件,结合 MyBatis 动态SQL,实现一个支持多条件查询的分页功能。

整个功能分为四层:

  • Controller层:接收请求参数,返回响应数据
  • Service层:业务逻辑处理,分页参数设置,结果封装
  • Mapper层:数据库交互,动态SQL查询
  • 数据模型设计:实体类、参数封装类、结果封装类

二、数据模型设计

在开始编写业务逻辑之前,我们需要先设计好数据模型。根据接口文档和业务需求,我们需要设计以下四个核心类:

2.1 实体类(Clazz)

实体类对应数据库表结构,用于封装从数据库查询到的员工数据。

设计思路

  • 根据接口文档的"响应数据"中 data.rows 属性封装的字段来设计
  • 包含员工基本信息(id、name、gender等)
  • 包含关联信息(部门ID、部门名称)

技术要点

  • 使用 @Data 注解(Lombok)自动生成getter/setter方法,简化代码
  • 日期类型使用 Java 8 的 LocalDate 和 LocalDateTime

2.2 查询参数封装类(ClazzQueryParam)

为什么需要这个类?

前端传递的参数较多(姓名、性别、入职时间范围、分页参数等),如果直接在Controller方法中传递多个参数,后续维护和扩展会非常麻烦。因此,我们将所有查询参数封装到一个对象中。

前端样貌

一般是页面上有几个特定的搜索框,用户可以在框内输入不同选项来筛选,最后符合条件的记录会被渲染出来。这些搜索框对应的就是我们的查询参数。

设计思路

  • 根据接口文档的"请求参数"来设计
  • 包含查询条件(name、gender、begin、end)
  • 包含分页参数(page、pageSize)
  • 技术要点

  • @DateTimeFormat 注解用于指定日期参数的接收格式
  • 所有查询条件都是可选的,支持灵活组合
  • 分页参数 page 和 pageSize 为必填项

2.3 分页结果封装类(PageResult)

为什么需要泛型设计?

分页查询的返回结果包含两部分:

  1. 总记录数(total)
  2. 当前页的数据列表(rows)

这个结构是通用的,不限于员工查询。使用泛型设计可以让这个类复用于其他分页场景(如部门列表、订单列表等)。

核心代码结构

设计思路

  • total:总记录数,用于前端计算总页数
  • rows:使用泛型 List<T> 封装数据库查询到的一条条结果

2.4 统一响应格式(Result)

为了规范化API响应格式,我们设计了统一的响应结果类:

设计思路

  • 提供静态工厂方法 success() 和 error(),简化调用
  • code 字段:1 表示成功,0 表示失败
  • 统一格式便于前端统一处理响应数据

三、Controller层实现

3.1 核心职责

ontroller层主要完成两件事:

  1. 接收查询参数:前端传来的Query参数对象
  2. 返回成功响应数据Result:在Result的data属性中封装PageResult对象

3.2 实现代码

四、Service层实现

4.1 核心职责

Service层完成三件事:

  1. **设置分页参数 **:使用PageHelper设置page和pageSize
  2. **调用Mapper层 **:执行SQL查询
  3. **封装PageResult对象 **:将查询结果封装为分页结果对象

4.2 实现代码

4.3 PageHelper工作原理

**问题 **:page和pageSize在哪里用到?

**答案 **:在Service层的第一行代码中用到:

PageHelper.startPage(empQueryParam.getPage(), empQueryParam.getPageSize());

**PageHelper的分页原理 **:

  1. **拦截SQL **:PageHelper是MyBatis的物理分页插件,会在SQL执行前拦截SQL语句
  2. **改写SQL **:根据数据库类型,自动在SQL末尾添加 LIMIT 子句
    -- 假设 page=1, pageSize=10
    -- 原SQL: SELECT e.*, d.name FROM emp e LEFT JOIN dept d ...
    -- 被改写为: SELECT e.*, d.name FROM emp e LEFT JOIN dept d ... LIMIT 0, 10
    
    -- 假设 page=2, pageSize=10
    -- 被改写为: SELECT e.*, d.name FROM emp e LEFT JOIN dept d ... LIMIT 10, 10
    
  3. **查询总数 **:同时自动执行 SELECT COUNT(*) 查询获取总记录数
  4. **封装结果 **:查询结果会被自动包装为 Page<T> 对象

**关键点 **:

  • 必须在执行查询之前调用 PageHelper.startPage()
  • PageHelper只会对接下来执行的第一条SQL生效

五、Mapper层实现

5.1 核心职责

Mapper层的核心任务是:** 动态SQL查询**

5.2 为什么需要动态SQL?

**问题 **:用户的查询条件是可选的(可以选择传或者不传参数)

如果写死SQL:

SELECT e.*, d.name FROM emp e 
LEFT JOIN dept d ON e.dept_id = d.id 
WHERE e.name LIKE '%张%' 
  AND e.gender = 1 
  AND e.entry_date BETWEEN '2010-01-01' AND '2020-01-01'

问题:

  • 如果用户不传 name 参数,SQL会报错(WHERE条件不完整)
  • 如果用户只传 gender 参数,其他条件为空,SQL依然会执行所有条件判断

**解决方案 **:使用MyBatis的 <if> 标签动态拼接SQL

六、完整数据流转过程

为了更清晰地理解整个流程,我们来看一下完整的数据流转:

6.1 请求阶段

前端请求 → Controller层 → Service层 → Mapper层 → 数据库

详细步骤

  1. 前端发起请求

    GET /emps?name=张&gender=1&begin=2010-01-01&end=2020-01-01&page=1&pageSize=10
    
  2. Controller层接收

    • Spring MVC自动将请求参数封装到 EmpQueryParam 对象中
    • 调用Service层方法
  3. Service层处理

    • 调用 PageHelper.startPage(1, 10) 设置分页参数
    • 调用Mapper层查询方法
  4. Mapper层执行

    • MyBatis根据动态SQL生成最终SQL
    • PageHelper拦截SQL,添加 LIMIT 0, 10
    • 执行SQL查询

6.2 响应阶段

数据库 → Mapper层 → Service层 → Controller层 → 前端

详细步骤

  1. 数据库返回结果

    • 返回10条员工记录
    • PageHelper自动查询总记录数(如:total=100)
  2. Mapper层返回

    • 返回 List<Emp> 集合(实际是 Page<Emp> 对象)
  3. Service层封装

    • 将 List<Emp> 转换为 Page<Emp>
    • 提取 total 和 rows
    • 封装为 PageResult<Emp> 对象返回
  4. Controller层响应

    • 将 PageResult 封装到 Result 对象中
    • 返回JSON格式响应数据

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐