目录
- 1. MyBATis框架简介
- 2. 分页查询原理与应用场景
- 2.1 分页查询的基本原理
- 2.1.1 分页查询的定义
- 2.1.2 分页机制的工作流程
- 2.1.3 数据库层面的分页实现方式
- 2.2 分页查询的典型应用场景
- 2.2.1 Web应用中的数据列表展示
- 2.2.2 大数据量下的性能优化需求
- 2.2.3 分页在前后端交互中的作用
- 2.3 MyBatis中实现分页的方式概述
- 2.3.1 手动拼接SQL分页
- 2.3.2 使用PageHelper插件自动分页
- 2.3.3 分页插件与原生SQL的对比分析
- 3. PageHelper插件集成与配置
- 3.1 PageHelper插件概述
- 3.1.1 PageHelper的功能与优势
- 3.1.2 插件的版本选择与兼容性分析
- 3.2 PageHelper的引入与配置
- 3.2.1 Maven依赖的引入方式
- 3.2.2 在MyBatis配置文件中添加插件配置
- 3.2.3 Spring Boot项目中的自动配置方式
- 3.3 PageHelper的基本使用流程
- 3.3.1 分页前的准备工作
- 3.3.2 分页方法调用与结果返回
- 3.3.3 常见配置参数的使用说明
- 4. Mapper接口分页方法设计
- 4.1 Mapper接口的设计规范
- 4.1.1 接口命名与SQL映射关系
- 4.1.2 使用PageHelper进行分页的方法定义
- 4.2 分页SQL语句的编写与优化
- 4.2.1 动态SQL与分页结合使用
- 4.2.2 分页SQL的性能考量与优化技巧
- 4.3 分页接口的测试与验证
- 4.3.1 单元测试的设计与执行
- 4.3.2 分页结果的验证与异常处理
- 附录:分页流程图
- 小结
- 5. Service层分页逻辑实现
- 5.1 Service层职责与分页逻辑设计
- 5.1.1 业务逻辑与分页的结合方式
- 5.1.2 调用Mapper接口实现分页查询
- 5.2 分页数据的处理与封装
- 5.2.1 PageInfo对象的封装过程
- 5.2.2 分页数据的业务处理逻辑
- 5.3 Service层的异常处理与日志记录
- 5.3.1 分页失败的异常处理策略
- 5.3.2 日志记录与问题定位技巧
- 6. Controller层接口接收与返回处理
- 6.1 Controller层的接口设计原则
- 6.1.1 接口路径与请求方式的定义
- 6.1.2 请求参数的接收与校验机制
- 6.2 分页数据的返回格式设计
- 6.2.1 jsON格式的封装与统一返回
- 6.2.2 PageInfo对象在接口中的返回结构
- 6.3 接口测试与调试
- 6.3.1 Postman测试接口的使用
- 6.3.2 接口调用过程中的问题排查
- 7. PageInfo对象使用详解
- 7.1 PageInfo对象的结构与属性
- 7.1.1 PageInfo类的核心字段说明
- 7.1.2 分页信息的封装过程
- 7.2 PageInfo在前端展示中的应用
- 7.2.1 前端页面获取与解析PageInfo数据
- 7.2.2 分页控件的动态渲染与交互逻辑
- 7.3 PageInfo的扩展与自定义封装
- 7.3.1 自定义分页对象的封装方式
- 7.3.2 PageInfo与其他业务对象的整合使用
简介:MyBatis是一个强大的Java持久层框架,支持自定义SQL和高级映射。在处理大数据量场景时,分页查询是提升系统性能的重要手段。本案例以员工工资信息管理为例,详细讲解如何在IDEA中使用MyBatis结合PageHelper插件实现分页查询功能。内容涵盖PageHelper的引入方式、Mapper接口的定义、服务层与控制层的调用流程,以及前后端交互的分页数据结构。通过该实战案例,开发者可以快速掌握MyBatis分页查询的完整实现流程,提升开发效率与系统性能。

1. MyBatis框架简介
MyBatis 是一个轻量级但功能强大的持久层框架,它摒弃了传统的全自动ORM映射方式,转而提供灵活的 SQL 控制能力。通过 XML 配置文件或注解方式,开发者可以精准定义 SQL 语句,并实现 Java 对象与数据库记录之间的映射。其核心组件包括  SqlSessionFactory  、  SqlSession  、  Mapper  接口及 XML 映射文件,构成了一个松耦合、易扩展的数据访问层架构。在企业级开发中,MyBatis 凭借其高性能、易调试和灵活的 SQL 管理机制,被广泛应用于复杂业务场景下的数据持久化处理,为实现如分页查询等功能提供了坚实的基础。
2. 分页查询原理与应用场景
2.1 分页查询的基本原理
2.1.1 分页查询的定义
分页查询(Pagination Query)是指在处理大量数据时,将数据按照一定数量划分为多个“页”进行查询和展示的技术。在Web应用中,这种机制被广泛使用,尤其是在处理成千上万条数据时,避免一次性加载全部数据到前端,从而提升用户体验和系统性能。
分页的核心思想是: 每次只加载用户当前需要查看的数据,而不是全部数据 。通过这种方式,可以有效降低服务器压力,提升响应速度,并优化数据库查询效率。
2.1.2 分页机制的工作流程
分页机制的典型工作流程如下:
- 前端请求 :用户在页面上点击下一页、上一页或跳转页码。
- 参数传递 :前端将当前页码(pageNum)和每页显示数量(pageSize)作为参数传递给后端接口。
- 后端处理 :- 解析请求参数 pageNum 和 pageSize;- 构建带分页条件的 SQL 查询语句;- 执行查询,获取当前页数据;- 同时获取总记录数用于计算总页数;
- 结果返回 :将当前页数据和分页信息(如总记录数、总页数)封装后返回给前端;
- 前端展示 :前端根据返回数据展示当前页数据,并渲染分页控件。
2.1.3 数据库层面的分页实现方式
不同的数据库系统支持的分页语法略有不同。以下是主流数据库的分页实现方式:
| 数据库类型 | 分页语法示例 | 说明 | 
|---|---|---|
| mysql | SELECT * FROM users LIMIT 10 OFFSET 20  | LIMIT 指定每页条数, OFFSET 指定偏移量 | 
| PostgreSQL | SELECT * FROM users LIMIT 10 OFFSET 20  | 与MySQL语法一致 | 
| oracle | SELECT * FROM (SELECT A.*, ROWNUM RN FROM (SELECT * FROM users) A WHERE ROWNUM <= 20) WHERE RN > 10  | 使用嵌套子查询和ROWNUM实现分页 | 
| SQL Server | SELECT * FROM users ORDER BY id OFFSET 10 ROWS FETCH NEXT 20 ROWS ONLY  | 使用OFFSET和FETCH实现分页 | 
以MySQL为例的分页SQL解析:
SELECT * FROM users LIMIT 10 OFFSET 20;
- LIMIT 10:每页显示10条数据;
- OFFSET 20:从第21条数据开始(即跳过前20条);
该SQL语句将返回第3页的数据(假设每页10条),即第21~30条记录。
2.2 分页查询的典型应用场景
2.2.1 Web应用中的数据列表展示
在Web应用中,数据列表是常见的展示形式,例如用户管理、订单列表、商品信息等。由于数据量可能非常庞大,一次性加载所有数据不仅影响用户体验,还会造成资源浪费。
分页在数据列表中的作用:
- 减少页面加载时间;
- 提高页面响应速度;
- 提升用户体验,避免信息过载;
- 便于数据的浏览与导航。
示例:
一个电商系统中,商品总数为10万条。若一次性加载所有商品信息,页面加载时间将极长,甚至导致浏览器卡顿。使用分页机制后,可以每次只加载100条数据,用户点击翻页时再加载后续数据。
2.2.2 大数据量下的性能优化需求
在处理大数据量时,数据库查询的性能尤为重要。如果不对查询进行分页限制,可能会导致以下问题:
- 查询响应时间长 :全表扫描会消耗大量资源;
- 内存占用高 :一次性加载大量数据可能超出内存限制;
- 数据库连接阻塞 :长时间的查询会占用数据库连接,影响其他操作。
解决方案:
使用分页查询可以有效限制返回数据量,从而:
- 减少数据库扫描的数据量;
- 降低I/O和CPU使用率;
- 缩短查询响应时间;
- 提高并发处理能力。
性能优化技巧:
- 使用索引字段作为排序条件;
- 避免使用 SELECT *,只查询需要的字段;
- 对查询条件进行合理索引;
- 分页深度优化(如游标分页)。
2.2.3 分页在前后端交互中的作用
分页查询不仅是一种数据库层面的技术,它在前后端交互中也扮演着重要角色。
前端视角:
- 分页控件(如“上一页”、“下一页”、“跳转页码”)提供良好的用户交互体验;
- 支持动态加载数据,实现懒加载或无限滚动;
- 提供分页状态管理(如当前页、总页数、总记录数);
后端视角:
- 分页接口设计规范统一;
- 接口返回结构标准化(如包含数据列表、总记录数、当前页码等);
- 分页信息封装为对象(如PageInfo)以便前端处理;
前后端交互流程示意图(mermaid):
sequenceDiagram
    用户->>前端: 点击下一页
    前端->>后端: 发送pageNum=2, pageSize=10
    后端->>数据库: 执行分页SQL查询
    数据库-->>后端: 返回第2页数据
    后端-->>前端: 返回JSON格式分页结果
    前端-->>用户: 展示第2页数据
2.3 MyBatis中实现分页的方式概述
2.3.1 手动拼接SQL分页
手动拼接SQL是最原始的分页实现方式,开发者需要在SQL语句中根据页码和每页大小动态计算偏移量并拼接LIMIT/OFFSET。
示例代码(MyBatis XML):
<select id="selectUsers" resultType="User">
    SELECT * FROM users
    <where>
        <if test="name javascript!= null">
            AND name LIKE CONCAT('%', #{name}, '%')
        </if>
    </where>
    LIMIT #{pageSize} OFFSET #{offset}
</select>
Java调用示例:
int pageNum = 2; int pageSize = 10; int offset = (pageNum - 1) * pageSize; List<User> users = userMapper.selectUsers(offset, pageSize);
优点:
- 灵活,适用于各种数据库;
- 无需依赖第三方插件;
缺点:
- 需要手动计算偏移量;
- 不利于代码复用;
- 分页逻辑分散在业务代码中;
2.3.2 使用PageHelper插件自动分页
PageHelper是MyBatis官方推荐的分页插件,它可以自动拦截SQL并添加分页语句,大大简化了分页逻辑的实现。
使用示例:
PageHelper.startPage(pageNum, pageSize); List<User> users = userMapper.selectAll(); PageInfo<User> pageInfo = new PageInfo<>(users);
对应的SQL执行过程:
SELECT * FROM users LIMIT 10 OFFSET 10; SELECT COUNT(*) FROM users;
优点:
- 简洁高效,一行代码实现分页;
- 自动处理COUNT查询;
- 支持多种数据库;
- 与MyBatis无缝集成;
缺点:
- 依赖PageHelper插件;
- 对某些复杂查询支持有限;
2.3.3 分页插件与原生SQL的对比分析
| 特性 | 原生SQL手动分页 | PageHelper插件分页 | 
|---|---|---|
| 实现方式 | 手动拼接LIMIT/OFFSET | 自动拦截并修改SQL | 
| 分页逻辑 | 分散在业务代码中 | 集中统一处理 | 
| 维护成本 | 高,容易出错 | 低,易于维护 | 
| 支持COUNT查询 | 需手动编写 | 自动添加 | 
| 灵活性 | 高,可自定义 | 依赖插件配置 | 
| 性能影响 | 无额外开销 | 插件有一定性能开销 | 
| 推荐场景 | 简单查询、数据库兼容要求高 | 中大型项目、快速开发 | 
结论:
- 对于小型项目或特定数据库兼容需求,可以使用原生SQL手动分页;
- 对于中大型项目、快速开发和标准化分页处理,推荐使用PageHelper插件;
- 在使用PageHelper时,需注意其对复杂查询(如JOIN、子查询)的兼容性,并结合实际情况选择使用方式。
3. PageHelper插件集成与配置
在实际开发中,处理大量数据时分页查询是必不可少的。PageHelper 是 MyBatis 中非常流行的一款分页插件,它能够简化分页逻辑,避免手动拼接 SQL 分页语句的复杂性。本章将深入讲解如何将 PageHelper 插件集成到项目中,并进行相关配置与使用,为后续章节中分页方法的实现打下坚实基础。
3.1 PageHelper插件概述
PageHelper 是一个专为 MyBatis 设计的分页插件,能够自动识别 SQL 语句并进行分页处理,极大提升了开发效率和代码可维护性。
3.1.1 PageHelper的功能与优势
PageHelper 提供了以下核心功能:
- 自android动分页 :通过简单的 API 调用即可实现 SQL 分页。
- 多数据库支持 :支持 MySQL、Oracle、SQL Server、PostgreSQL 等主流数据库。
- 灵活配置 :允许配置分页参数、合理化分页、默认页面大小等。
- 兼容性好 :与 Spring、Spring Boot、MyBatis 注解和 XML 映射方式兼容良好。
- 性能优化 :底层采用拦截器机制,对 SQL 进行改写,减少冗余操作。
相较于手动拼接 SQL 分页,使用 PageHelper 的优势在于:
- 开发效率高 :减少重复代码。
- 可维护性强 :集中管理分页逻辑。
- 扩展性好 :支持多种数据库和自定义分页策略。
3.1.2 插件的版本选择与兼容性分析
PageHelper 的版本更新频繁,不同版本与 MyBatis 和 Spring Boot 的兼容性略有不同。常见版本如下:
| PageHelper 版本 | MyBatis 版本要求 | Spring Boot 版本建议 | 说明 | 
|---|---|---|---|
| 5.2.0 | ≥3.4.0 | ≥2.0.0 | 最新稳定版,推荐使用 | 
| 5.1.11 | ≥3.4.0 | ≥1.5.0 | 稳定版本,广泛使用 | 
| 4.x | 3.x | 1.x | 旧版,不推荐新项目使用 | 
在选择版本时,应优先考虑与当前项目依赖版本的匹配性。推荐使用  5.2.0  或  5.1.11  版本。
3.2 PageHelper的引入与配置
为了在项目中使用 PageHelper,需要将其作为依赖引入,并在 MyBatis 或 Spring Boot 配置文件中进行相应设置。
3.2.1 Maven依赖的引入方式
在  pom.xml  中添加如下依赖:
<!-- MyBatis PageHelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>5.2.0</version>
</dependency>
说明:
- pagehelper-spring-boot-starter是 Spring Boot 项目专用的 starter 模块,自动完成插件的配置。
- 若非 Spring Boot 项目,可使用 pagehelper模块,并手动配置插件。
3.2.2 在MyBatis配置文件中添加插件配置
对于非 Spring Boot 项目(传统 Spring + MyBatis),需在  mybatis-config.xml  中手动添加插件配置:
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!-- 可选参数 -->
        <property name="helperDialect" value="mysql"/>
        <property name="reasonable" value="true"/>
        <property name="supportMethodsArguments" value="true"/>
        <property name="params" value="count=countSql"/>
    </plugin>
</plugins>
参数说明:
| 参数名 | 含义 | 示例值 | 
|---|---|---|
| helperDialect | 指定数据库方言 | mysql、oracle、sqlserver | 
| reasonable | 是否启用合理化分页(页码超出范围时自动调整) | true、false | 
| supportMethodsArguments | 是否支持通过 Mapper 接口参数传递分页信息 | true | 
| params | 配置 count 查询的参数名 | count=countSql | 
3.2.3 Spring Boot项目中的自动配置方式
在 Spring Boot 项目中,除了引入依赖,还可以通过  application.yml  或  application.properties  文件进行配置:
pagehelper: helper-dialect: mysql reasonable: true support-methods-arguments: true params: count=countSql
说明:
- helper-dialect:指定数据库类型,用于分页 SQL 的自动改写。
- reasonable:开启合理化分页,如当前页码大于最大页码时,自动返回最后一页数据。
- support-methods-arguments:启用接口方法参数中分页信息的自动识别。
- params:定义 count 查询参数名称。
3.3 PageHelper的基本使用流程
PageHelper 的使用流程主要包括分页前的准备、分页方法的调用以及分页结果的获取。
3.3.1 分页前的准备工作
在调用分页方法之前,必须先调用  PageHelper.startPage(pageNum, pageSize)  方法,该方法会设置当前线程的分页上下文。
import com.github.pagehelper.PageHelper;
import java.util.List;
public List<User> getUsers(int pageNum, int pageSize) {
    // 开启分页
    PageHelper.startPage(pageNum, pageSize);
    // 查询用户列表(自动分页)
    return userMapper.selectAll();
}
说明:
- pageNum:当前页码,从 1 开始。
- pageSize:每页显示的数据条数。
- 该方法必须在查询语句之前调用,否则分页失效。
3.3.2 分页方法调用与结果返回
调用  PageHelper.startPage()  后,紧接着执行查询语句即可。PageHelper 会自动拦截 SQL,并添加  LIMIT  子句实现分页。
执行查询后,可以通过  PageInfo<T>  类封装分页信息:
import com.github.pagehelper.PageInfo;
public PageInfo<User> getUsers(int pageNum, int pageSize) {
    PageHelper.startPage(pageNum, pageSize);
    List<User> users = userMapper.selectAll();
    return new PageInfo<>(users);
}
PageInfo  包含了丰富的分页信息,如总记录数、当前页、总页数等,便于前端展示。
3.3.3 常见配置参数的使用说明
| 参数 | 作用 | 使用示例 | 
|---|---|---|
| pageNum  | 当前页码 | PageHelper.startPage(2, 10);  | 
| pageSize  | 每页数量 | PageHelper.startPage(1, 20);  | 
| count  | 是否执行 count 查询 | 默认为 true,可通过  PageHelp编程客栈er.startPage(pageNum, pageSize, false) 关闭 | 
| reasonable  | 是否启用合理化分页 | 在配置文件中设置为  true  | 
| pageSizeZero  | 是否允许 pageSize 为 0 | 可设置为  true 表示不分页 | 
示例:关闭 count 查询
在某些场景下,如果已知总记录数,可避免重复执行 count 查询:
PageHelper.startPage(1, 10, false); List<User> users = userMapper.selectAll();
此时 PageHelper 不会生成 count SQL,从而提高性能。
示例:分页结果封装为 PageInfo
PageInfo<User> pageInfo = new PageInfo<>(users);
System.out.println("当前页码:" + pageInfo.getPageNum());
System.out.println("每页数量:" + pageInfo.getPageSize());
System.out.println("总记录数:" + pageInfo.getTotal());
System.out.println("总页数:" + pageInfo.getPages());
输出结果如下:
当前页码:1
每页数量:10总记录数:123总页数:13
流程图:PageHelper分页执行流程
graph TD
    A[调用PageHelper.startPage] --> B{判断是否合理化分页}
    B -->|是| C[自动调整页码]
    B -->|否| D[使用原始页码]
    C --> E[生成分页SQL]
    D --> E
    E --> F[执行SQL查询]
    F --> G[返回分页数据]
    G --> H[创建PageInfo对象]
    H --> I[返回给调用方]
该流程图清晰地展示了 PageHelper 分页的核心执行流程,从开始分页到最终封装返回数据的全过程。
通过本章的学习,我们了解了 PageHelper 的功能与优势,掌握了其在 Maven 项目中的引入方式,以及在 MyBatis 和 Spring Boot 中的配置方法,并详细讲解了其基本使用流程及常见参数设置。下一章将围绕 Mapper 接口设计分页方法展开,进一步深入 MyBatis 分页的实践应用。
4. Mapper接口分页方法设计
在 MyBatis 框架中,Mapper 接口是实现数据库操作的核心部分。设计良好的 Mapper 接口不仅能够提高代码的可维护性,还能有效支持分页查询功能。本章将深入探讨 Mapper 接口中分页方法的设计规范、分页 SQL 的编写与优化策略,以及接口测试与验证方法,帮助开发者构建高效、稳定的分页机制。
4.1 Mapper接口的设计规范
4.1.1 接口命名与SQL映射关系
在设计 Mapper 接口时,良好的命名规范有助于提高代码的可读性和维护性。通常建议使用  IEntityMapper  的命名方式,其中  Entity  表示操作的实体对象,例如  IUserMapper  、  编程客栈IOrderMapper  。
接口中的方法名应与 XML 映射文件中的 SQL ID 保持一致,以确保 MyBatis 能正确绑定方法与 SQL 语句。例如:
public interface IUserMapper {
    List<User> selectAllUsers();
}
对应的 XML 文件中:
<select id="selectAllUsers" resultType="User">
    SELECT * FROM users
</select>
这种命名方式不仅提高了代码的可读性,也便于后期维护和调试。
4.1.2 使用PageHelper进行分页的方法定义
在使用 PageHelper 插件进行分页时,Mapper 接口中的方法设计需遵循一定的规范。PageHelper 通过拦截查询语句,自动为其添加分页逻辑,因此接口方法只需返回  List<T>  类型即可,PageHelper 会在执行过程中自动封装分页信息。
示例代码如下:
public interface IUserMapper {
    List<User> selectAllUsers();
}
在调用该方法前,需先调用  PageHelper.startPage(pageNum, pageSize)  方法启动分页:
PageHelper.startPage(1, 10); List<User> users = userMapper.selectAllUsers();
PageHelper 会自动对  selectAllUsers()  方法的查询结果进行分页处理,并封装成  PageInfo  对象,便于后续使用。
4.2 分页SQL语句的编写与优化
4.2.1 动态SQL与分页结合使用
在实际开发中,经常需要根据不同的条件进行分页查询。MyBatis 提供了  <if>  、  <choose>  、  <where>  等标签,用于构建动态 SQL,从而实现灵活的查询逻辑。
例如,以下是一个带有动态查询条件的分页 SQL 示例:
<select id="selectUsersByCondition" resultType="User">
    SELECT * FROM users
    <where>
        <if test="username != null and username != ''">
            AND username LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="email != null and email != ''">
            AND email = #{email}
        </if>
    </where>
</select>
在 Java 代码中调用:
PageHelper.startPage(1, 10);
List<User> users = userMapper.selectUsersByCondition("john", null);
该查询会根据传入的参数动态生成 SQL,确保分页结果的准确性。
4.2.2 分页SQL的性能考量与优化技巧
分页查询在处理大数据量时容易造成性能问题。以下是一些常见的优化技巧:
- 避免使用 SELECT *:指定查询字段,减少不必要的数据传输。
- 使用索引 :确保分页字段(如主键或常用查询字段)上有合适的索引。
- 避免深分页问题  :对于 LIMIT offset, size这种方式,当 offset 值很大时会导致性能下降,建议采用“游标分页”或“基于时间戳”的方式。
- 合理设置分页大小 :避免一次请求返回过多数据,建议每页 10~50 条为宜。
示例优化后的 SQL:
<select id="selectUsersByCondition" resultType="User">
    SELECT id, username, email FROM users
    <where>
        <if test="username != null and username != ''">
            AND username LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="email != null and email != ''">
            AND email = #{email}
        </if>
    </where>
</select>
通过指定字段查询,减少数据库 IO 消耗,提升查询效率。
4.3 分页接口的测试与验证
4.3.1 单元测试的设计与执行
为了确保分页接口的正确性和稳定性,必须进行充分的单元测试。可以使用 JUnit 框架结合 Spring Boot Test 进行测试。
示例单元测试代码如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTest {
    @Autowired
    private IUserMapper userMapper;
    @Test
    public void testSelectUsersWithPagination() {
        PageHelper.startPage(1, 10);
        List<User> users = userMapper.selectAllUsers();
        Assert.notEmpty(users, "用户列表不能为空");
        Assert.isTrue(users.size() <= 10, "返回结果数量不应超过每页限制");
    }
}
该测试方法验证了分页查询是否返回了正确的数据量,并确保接口逻辑无误。
4.3.2 分页结果的验证与异常处理
在实际运行中,可能会出现分页失败、SQL 错误等情况。因此,在测试中还需验证异常处理逻辑。
例如,测试当传入非法页码时是否抛出异常:
@Test(expected = RuntimeException.class)
public void testInvalidPageNumber() {
    PageHelper.startPage(-1, 10); // 无效页码
    userMapper.selectAllUsers();
}
此外,还可以通过日志记录、异常捕获等方式进行异常处理,提升系统的健壮性。
附录:分页流程图
以下是分页查询在 MyBatis 中的完整执行流程图:
graph TD
    A[调用Mapper方法] --> B{PageHelper是否启动}
    B -->|是| C[拦截SQL并添加分页语句]
    C --> D[执行SQL查询]
    D --> E[封装结果为PageInfo]
    B -->|否| F[直接执行SQL查询]
    F --> G[返回原始结果]
该流程图清晰地展示了 PageHelper 在分页查询中的作用机制,有助于理解其工作原理。
小结
本章详细介绍了在 MyBatis 中设计 Mapper 接口进行分页查询的方法,包括接口命名规范、PageHelper 的使用方式、动态 SQL 的编写与优化技巧,以及接口的测试与异常处理。通过本章的学习,开发者可以掌握如何在实际项目中高效地实现分页功能,并优化其性能表现。
5. Service层分页逻辑实现
在Spring Boot架构中,Service层承担着核心业务逻辑的处理职责。在分页查询场景下,Service层不仅要协调Mapper层的数据获取,还需对分页结果进行封装与处理。本章将从Service层的分页逻辑设计、数据封装方式、异常处理机制等方面深入探讨,并通过代码实例展示完整的实现流程。
5.1 Service层职责与分页逻辑设计
Service层作为连接Controller与Mapper的桥梁,其设计直接影响分页查询的性能与可维护性。合理的逻辑设计能够提高系统的可扩展性与健壮性。
5.1.1 业务逻辑与分页的结合方式
在企业级应用中,分页查询往往不是单纯的数据库查询,而是需要结合业务规则进行过滤、排序或聚合。例如,在用户管理模块中,可能需要根据角色、状态等条件进行筛选后再分页。
public interface UserService {
    PageInfo<UserDTO> getUsersByRoleAndStatus(String role, String status, int pageNum, int pageSize);
}
在该接口中,  role  和  status  是业务筛选条件,  pageNum  和  pageSize  用于分页控制。Service层通过组合这些参数调用Mapper层的查询接口。
分页逻辑流程图(Mermaid格式)
graph TD
    A[Controller层接收请求] --> B[调用Service层方法]
    B --> C[Service层封装参数]
    C --> D[调用PageHelper.startPage()]
    D --> E[执行Mapper层查询]
    E --> F[封装PageInfo对象]
    F --> G[返回分页结果]
5.1.2 调用Mapper接口实现分页查询
Service层调用Mapper接口前,需使用PageHelper插件开启分页功能。以下是一个典型的调用示例:
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
    @Override
    public PageInfo<UserDTO> getUsersByRoleAndStatus(String role, String status, int pageNum, int pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        List<UserDTO> users = userMapper.selectByRoleAndStatus(role, status);
        return new PageInfo<>(users);
    }
}
代码逐行分析:
- 第7行 :注入UserMapper,用于执行数据库查询。
- 第11行  :调用 PageHelper.startPage()方法,指定当前页码和每页记录数。
- 第12行 :调用Mapper接口的查询方法,返回当前页的数据列表。
- 第13行 :使用MyBatis PageHelper提供的PageInfo类封装分页信息,包括总记录数、总页数、当前页数据等。
5.2 分页数据的处理与封装
分页查询的结果通常包含当前页数据、总记录数、页码信息等。Service层需对这些数据进行封装,并可能附加业务逻辑处理。
5.2.1 PageInfo对象的封装过程
PageInfo是PageHelper提供的分页信息封装类,包含以下核心字段:
| 字段名 | 类型 | 说明 | 
|---|---|---|
| pageNum | int | 当前页码 | 
| pageSize | int | 每页记录数 | 
| size | int | 当前页实际记录数 | 
| total | long | 总记录数 | 
| pages | int | 总页数 | 
| list | List | 当前页的数据列表 | 
| isFirstPage | boolean | 是否为第一页 | 
| isLastPage | boolean | 是否为最后一页 | 
封装示例:
PageInfo<UserDTO> pageInfo = new PageInfo<>(users);
该语句自动计算出总页数、是否为首页/尾页等信息,开发者可直接使用这些字段进行页面展示。
5.2.2 分页数据的业务处理逻辑
在实际业务中,分页结果可能需要进行额外处理,例如:
- 数据转换:将数据库实体转换为DTO(数据传输对象)。
- 数据脱敏:对敏感字段进行隐藏或加密。
- 数据聚合:对查询结果进行统计或分组。
以下是一个带有数据转换与脱敏处理的示例:
@Override
public PageInfo<UserDTO> getUsersByRoleAndStatus(String role, String status, int pageNum, int pageSize) {
    PageHelper.startPage(pageNum, pageSize);
    List<User> users = userMapper.selectByRoleAndStatus(role, status);
    // 数据转换与脱敏
    List<UserDTO> dtoList = users.stream()
        .map(user -> {
            UserDTO dto = new UserDTO();
            dto.setId(user.getId());
            dto.setUsername(user.getUsername());
            dto.setEmail("****@example.com"); // 脱敏处理
            return dto;
        })
        .collect(Collectors.toList());
    return new PageInfo<>(dtoList);
}
代码逻辑说明:
- 第6~13行 :将User实体转换为UserDTO,并对email字段进行脱敏处理。
- 第14行 :封装为PageInfo对象,便于Controller层返回。
5.3 Service层的异常处理与日志记录
在分页操作中,可能出现SQL执行异常、参数校验失败、空指针等错误。良好的异常处理机制与日志记录策略有助于快速定位问题。
5.3.1 分页失败的异常处理策略
建议使用Spring的全局异常处理器统一处理异常,以下为Service层的局部处理示例:
@Override
public PageInfo<UserDTO> getUsersByRoleAndStatus(String role, String status, int pageNum, int pageSize) {
    try {
        PageHelper.startPage(pageNum, pageSize);
        List<User> users = userMapper.selectByRoleAndStatus(role, status);
        return new PageInfo<>(convertToDTO(users));
    } catch (Exception e) {
        // 捕获异常并封装为业务异常
        throw new BusinessExceptpythonion("分页查询失败:" + e.getMessage(), e);
    }
}
异常处理流程图(Mermaid格式)
graph TD
    A[Service层执行分页] --> B{是否发生异常?}
    B -->|是| C[捕获异常]
    C --> D[封装为业务异常]
    D --> E[抛出异常]
    B -->|否| F[正常返回PageInfo]
5.3.2 日志记录与问题定位技巧
建议在Service层引入日志记录器(如Logback、SLF4J),记录关键参数和执行结果:
private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
@Override
public PageInfo<UserDTO> getUsersByRoleAndStatus(String role, String status, int pageNum, int pageSize) {
    logger.info("开始分页查询,角色:{}, 状态:{}, 当前页:{}, 每页记录数:{}", role, status, pageNum, pageSize);
    try {
        PageHelper.startPage(pageNum, pageSize);
        List<User> users = userMapper.selectByRoleAndStatus(role, status);
        PageInfo<UserDTO> pageInfo = new PageInfo<>(convertToDTO(users));
        logger.info("分页查询成功,总记录数:{}", pageInfo.getTotal());
        return pageInfo;
    } catch (Exception e) {
        logger.error("分页查询失败:", e);
        throw new BusinessException("分页查询失败:" + e.getMessage(), e);
    }
}
日志输出示例:
INFO UserServiceImpl - 开始分页查询,角色:admin, 状态:active, 当前页:1, 每页记录数:10 INFO UserServiceImpl - 分页查询成功,总记录数:50
日志记录建议:
- 记录请求参数、分页参数、执行结果等关键信息。
- 使用结构化日志格式(如JSON)便于日志分析系统解析。
- 在异常发生时,记录堆栈信息以便快速定位问题。
本章系统讲解了Service层在分页查询中的核心职责,包括分页逻辑设计、数据封装方式以及异常处理与日志记录策略。通过代码实例与流程图的结合,展示了如何在实际开发中高效实现分页功能,并确保系统的稳定性与可维护性。下一章将重点介绍Controller层如何接收分页请求并返回统一格式的响应。
6. Controller层接口接收与返回处理
在基于MyBatis和PageHelper构建的分页系统中,Controller层作为前后端交互的核心环节,承担着接收请求参数、调用业务逻辑并返回结构化响应的关键任务。本章将深入探讨Controller层接口设计的规范、分页数据的返回格式设计,并结合实际代码示例说明如何通过接口测试工具(如Postman)进行调试和问题排查。
6.1 Controller层的接口设计原则
在Spring Boot框架中,Controller层主要通过  @RestController  注解实现,其设计需遵循RESTful风格,同时兼顾接口的健壮性和可维护性。
6.1.1 接口路径与请求方式的定义
接口路径的设计应遵循清晰、简洁的原则,通常以名词复数形式表示资源集合。例如:
@GetMapping("/users")
public PageInfo<User> getUsers(@RequestParam int pageNum, @RequestParam int pageSize) {
    return userService.getUsersByPage(pageNum, pageSize);
}
- 请求方式 :GET 用于获取资源,POST 用于提交数据。
- 路径命名  : /users表示用户资源,/orders表示订单资源,遵循小写复数命名。
- 版本控制  :建议在URL中加入版本号,如 /v1/users,便于后续接口升级。
6.1.2 请求参数的接收与校验机制
Controller层接收参数时,应进行基本的校验,防止非法输入导致系统异常。Spring Boot提供了  @Valid  注解结合  javax.validation  实现参数校验。
示例:带参数校验的分页接口
@RestController
@RequestMapping("/v1/users")
public class UserController {
    @Autowired
    private UserService userService;
    @GetMapping
    public ResponseEntity<PageInfo<User>> getUsers(
            @RequestParam @Min(1) int pageNum,
            @RequestParam @Min(1) @Max(100) int pageSize) {
        PageInfo<User> pageInfo = userService.getUsersByPage(pageNum, pageSize);
        return ResponseEntity.ok(pageInfo);
    }
}
- @Min(1):确保页码至少为1。
- @Max(100):限制每页最多100条数据,防止性能问题。
- 使用 ResponseEntity封装响应,增强接口返回的可扩展性。
6.2 分页数据的返回格式设计
为了前后端交互的一致性和统一性,Controller层返回的分页数据应具备标准化结构,通常采用JSON格式,并封装统一的响应对象。
6.2.1 JSON格式的封装与统一返回
建议使用统一的响应封装类  ResponseResult  ,其结构如下:
public class ResponseResult<T> {
    private int code;
    private String message;
    private T data;
    // 构造方法、getters、setters
}
结合PageInfo返回:
@GetMapping
public ResponseResult<PageInfo<User>> getUsers(
        @RequestParam @Min(1) int pageNum,
        @RequestParam @Min(1) @Max(100) int pageSize) {
    PageInfo<User> pageInfo = userService.getUsersByPage(pageNum, pageSize);
    return ResponseResult.success(pageInfo);
}
响应示例:
{
  "code": 200,
  "message": "操作成功",
  "data": {
    "pageNum": 1,
    "pageSize": 10,
    "total": 150,
    "pages": 15,
    "list": [
      {"id": 1, "name": "张三", "email": "zhangsan@example.com"},
      ...
    ]
  }
}
- code:状态码,如200表示成功。
- message:描述性信息,便于调试。
- data:封装分页数据对象- PageInfo。
6.2.2 PageInfo对象在接口中的返回结构
PageInfo  对象是PageHelper提供的分页结果封装类,包含以下核心字段:
| 字段名 | 类型 | 描述 | 
|---|---|---|
| pageNum | int | 当前页码 | 
| pageSize | int | 每页数量 | 
| total | long | 总记录数 | 
| pages | int | 总页数 | 
| list | List | 当前页的数据集合 | 
| isFirstPage | boolean | 是否为首页 | 
| isLastPage | boolean | 是否为尾页 | 
mermaid流程图:Controller层数据流向
graph TD
    A[前端请求] --> B[Controller接收参数]
    B --> C{参数校验是否通过?}
    C -->|是| D[调用Service获取PageInfo]
    D --> E[封装ResponseResult]
    E --> F[返回JSON格式数据]
    C -->|否| G[返回错误信息]
6.3 接口测试与调试
接口开发完成后,必须进行充分的测试与调试,确保分页逻辑正确、数据返回格式一致、异常处理得当。
6.3.1 Postman测试接口的使用
使用Postman可以快速测试RESTful接口,以下是测试步骤:
- 打开Postman ,新建请求。
- 设置请求方式 :GET。
- 输入请求地址  :例如 http://localhost:8080/v1/users?pageNum=1&pageSize=10。
- 发送请求 ,查看返回的JSON结构是否符合预期。
- 测试异常情况  :如 pageNum=0、pageSize=200,验证是否返回400错误及提示信息。
示例截图说明(文字描述)
假设Postman界面左侧为请求输入区域,输入URL和参数后点击“Send”按钮,右侧显示响应结果。响应体应包含完整的PageInfo数据结构,并且状态码为200或400。
6.3.2 接口调用过程中的问题排查
常见问题及排查方法如下:
| 问题现象 | 原因分析 | 解决方法 | 
|---|---|---|
| 返回数据为空 | SQL语句错误、PageHelper未生效 | 检查SQL是否执行,日志输出是否开启 | 
| 分页不准确 | pageNum或pageSize参数传递错误 | 添加参数校验,确保参数合法 | 
| 接口响应超时 | 数据量过大或SQL未优化 | 检查SQL执行计划,添加索引,优化查询语句 | 
| PageInfo字段缺失或异常 | PageHelper版本兼容性问题 | 升级PageHelper版本,或更换Spring Boot版本 | 
示例:日志输出辅助调试
在Spring Boot中启用SQL日志输出:
# application.yml
logging:
  level:
    com.example.mapper: debug
输出示例:
DEBUG c.e.m.UserMapper.getUsersByPage - ==> Preparing: SELECT * FROM users LIMIT 0,10 DEBUG c.e.m.UserMapper.getUsersByPage - ==> Parameters: DEBUG c.e.m.UserMapper.getUsersByPage - <== Total: 10
通过日志可以确认分页是否生效,SQL是否被PageHelper拦截并修改。
本章从Controller层接口设计原则出发,深入讲解了分页接口的路径定义、参数接收与校验机制,并详细说明了如何设计统一的JSON返回格式。通过引入  ResponseResult  与  PageInfo  对象,实现结构化数据返回。最后,结合Postman测试与常见问题排查技巧,帮助开发者高效完成接口调试与问题定位。
7. PageInfo对象使用详解
7.1 PageInfo对象的结构与属性
PageInfo 是 PageHelper 提供的一个分页数据封装类,它封装了当前页的数据信息以及与分页相关的各种元数据。在使用 PageHelper 进行分页查询后,通过调用  PageHelper.startPage()  方法后紧跟查询语句,会自动将结果封装为 PageInfo 对象。
7.1.1 PageInfo类的核心字段说明
PageInfo 类包含以下常用字段:
| 字段名 | 类型 | 描述 | 
|---|---|---|
| pageNum | int | 当前页码 | 
| pageSize | int | 每页记录数 | 
| size | int | 当前页的实际数据条数 | 
| startRow | int | 当前页第一个数据的行号 | 
| endRow | int | 当前页最后一个数据的行号 | 
| total | long | 总记录数 | 
| pages | int | 总页数 | 
| list | List<?> | 当前页的数据集合 | 
| prePage | int | 上一页页码 | 
| nextPage | int | 下一页页码 | 
| isFirstPage | boolean | 是否为第一页 | 
| isLastPage | boolean | 是否为最后一页 | 
| hASPreviousPage | boolean | 是否存在上一页 | 
| hasNextPage | boolean | 是否存在下一页 | 
7.1.2 分页信息的封装过程
在 Service 层调用 Mapper 查询后,PageHelper 会自动将查询结果封装成 PageInfo 对象。例如:
PageHelper.startPage(pageNum, pageSize); // 设置分页参数 List<User> users = userMapper.selectAll(); // 执行查询 PageInfo<User> pageInfo = new PageInfo<>(users); // 封装成PageInfo
上述代码中,  PageHelper.startPage()  会拦截下一条 SQL 查询,并自动添加分页逻辑。查询结果通过  PageInfo  构造函数进行封装,返回包含完整分页信息的对象。
7.2 PageInfo在前端展示中的应用
7.2.1 前端页面获取与解析PageInfo数据
在前后端分离架构中,Controller 层通常会将 PageInfo 对象以 JSON 格式返回给前端。例如:
@GetMapping("/users")
public ResponseEntity<PageInfo<User>> getUsers(@RequestParam int pageNum, @RequestParam int pageSize) {
    PageInfo<User> pageInfo = userService.getUsers(pageNum, pageSize);
    return ResponseEntity.ok(pageInfo);
}
前端(如 vue、React、Angular 等框架)接收到数据后,可以轻松解析并展示分页信息:
fetch(`/api/users?pageNum=1&pageSize=10`)
  .then(response => response.json())
  .then(data => {
    console.log('当前页码:', data.pageNum);
    console.log('总页数:', data.pages);
    console.log('用户列表:', data.list);
  });
7.2.2 分页控件的动态渲染与交互逻辑
前端可以基于 PageInfo 中的  pageNum  、  pages  、  hasPreviousPage  和  hasNextPage  等字段来动态渲染分页控件,并实现跳转逻辑。
例如,使用 Vue 渲染一个简单的分页组件:
<template>
  <div class="pagination">
    <button :disabled="!pageInfo.hasPreviousPage" @click="prevPage">上一页</button>
    <span>当前页:{{ pageInfo.pageNum }} / {{ pageInfo.pages }}</span>
    <button :disabled="!pageInfo.hasNextPage" @click="nextPage">下一页</button>
  </div>
</template>
<script>
export default {
  props: ['pageInfo'],
  methods: {
    prevPage() {
      this.$emit('change-page', this.pageInfo.pageNum - 1);
    },
    nextPage() {
      this.$emit('change-page', this.pageInfo.pageNum + 1);
    }
  }
}
</script>
通过监听按钮点击事件并重新请求对应页码的数据,实现页面切换。
7.3 PageInfo的扩展与自定义封装
7.3.1 自定义分页对象的封装方式
虽然 PageInfo 已经提供了丰富的字段,但在某些业务场景中,可能需要额外的字段或更灵活的数据结构。此时可以自定义一个分页响应类来封装 PageInfo 的数据。
例如:
public class CustomPageResponse<T> {
    private int currentPage;
    private int pageSize;
    private long totalElements;
    private int totalPages;
    private List<T> content;
    private boolean hasNext;
    private boolean hasPrevious;
    public static <T> CustomPageResponse<T> fromPageInfo(PageInfo<T> pageInfo) {
        CustomPageResponse<T> response = new CustomPageResponse<>();
        response.setCurrentPage(pageInfo.getPageNum());
        response.setPageSize(pageInfo.getPageSize());
        response.setTotalElements(pageInfo.getTotal());
        response.setTotalPages(pageInfo.getPages());
        response.setContent(pageInfo.getList());
        response.setHasNext(pageInfo.isHasNextPage());
        response.setHasPrevious(pageInfo.isHasPreviousPage());
        return response;
    }
    // Getters and Setters
}
在 Controller 中返回该对象:
@GetMapping("/users")
public ResponseEntity<CustomPageResponse<User>> getUsers(@RequestParam int pageNum, @RequestParam int pageSize) {
    PageInfo<User> pageInfo = userService.getUsers(pageNum, pageSize);
    CustomPageResponse<User> response = CustomPageResponse.fromPageInfo(pageInfo);
    return ResponseEntity.ok(response);
}
7.3.2 PageInfo与其他业务对象的整合使用
在实际业务中,分页信息可能需要与其它数据(如统计数据、筛选条件、权限信息等)一起返回。此时可以将 PageInfo 作为响应对象的一个字段进行封装。
例如:
public class UserPageResponse {
    private PageInfo<User> pageInfo;
    private int activeUserCount;
    private int totalUserCount;
    // 构造方法、Getters、Setters
}
Controller 返回示例:
@GetMapping("/users")
public ResponseEntity<UserPageResponse> getUsers(@RequestParam int pageNum, @RequestParam int pageSize) {
    PageInfo<User> pageInfo = userService.getUsers(pageNum, pageSize);
    int activeCount = userService.getActiveUserCount();
    int totalCount = pageInfo.getTotal();
    UserPageResponse response = new UserPageResponse();
    response.setPageInfo(pageInfo);
    response.setActiveUserCount(activeCount);
    response.setTotalUserCount(totalCount);
    return ResponseEntity.ok(response);
}
这样,前端可以在获取分页数据的同时,也获得其他业务信息,提升接口的灵活性和复用性。
到此这篇关于MyBatis分页查询实战案例详解的文章就介绍到这了,更多相关MyBatis分页查询内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
 
         
                                         
                                         
                                         
                                         
                                         
                                         
                                         
                                         加载中,请稍侯......
 加载中,请稍侯......
      
精彩评论