mybatis 分页(手把手讲解)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观
在现代 Web 应用开发中,分页功能是提升用户体验和优化系统性能的重要手段。无论是用户浏览商品列表、查看日志记录,还是处理大数据量的查询,分页都能有效减少单次数据传输量,降低服务器负载。作为 Java 领域广泛使用的持久层框架,MyBatis 提供了多种灵活的分页解决方案。本文将从基础概念到实战案例,逐步解析 mybatis 分页的实现原理与最佳实践,帮助开发者快速掌握这一核心技能。
一、分页的基本概念与核心逻辑
1.1 什么是分页?
分页是指将大量数据按固定数量划分为多个页面,用户通过选择页码或导航按钮逐步查看数据。例如,电商网站的商品列表通常以每页 20 件商品的形式展示,用户可以通过点击“下一页”跳转到后续数据。
1.2 分页的核心参数
分页功能通常涉及以下参数:
- 当前页码(page_number):用户请求的页码,通常从 1 或 0 开始。
- 每页显示数量(page_size):每页展示的数据条数,如 10、20 或 50 条。
- 总记录数(total_records):数据库中符合条件的总数据量,用于计算总页数。
1.3 分页的核心公式
计算总页数时,常用公式为:
总页数 = (总记录数 + 每页数量 - 1) / 每页数量
例如,总记录数为 23 条,每页显示 10 条,则总页数为 (23 + 10 -1)/10 = 3
页。
二、MyBatis 原生分页:直接操作 SQL
2.1 原生分页的实现原理
MyBatis 的原生分页通过 SQL 语句的 LIMIT
和 OFFSET
子句实现。其基本语法为:
SELECT * FROM 表名 ORDER BY 字段
LIMIT 每页数量 OFFSET 起始位置
其中:
LIMIT
指定每页返回的数据量。OFFSET
指定从第几条记录开始读取(起始位置从 0 开始)。
2.2 原生分页的代码示例
假设有一个 user
表,需要查询第 2 页的数据,每页显示 10 条记录:
// Mapper 接口
public interface UserMapper {
List<User> selectUsersByPage(@Param("offset") int offset, @Param("pageSize") int pageSize);
}
// 对应的 XML 映射文件
<select id="selectUsersByPage" resultType="User">
SELECT * FROM user
ORDER BY id
LIMIT #{pageSize} OFFSET #{offset}
</select>
2.3 参数计算与总记录数查询
为了获取总记录数,需单独执行一条查询语句:
// Mapper 接口
int countUsers();
// XML 映射文件
<select id="countUsers" resultType="int">
SELECT COUNT(*) FROM user
</select>
2.4 优缺点分析
优点:
- 直接使用 SQL 语法,无需额外依赖插件。
- 对数据库兼容性较好(如 MySQL、PostgreSQL 等)。
缺点:
- 需要手动计算
OFFSET
,容易出错(公式为offset = (page_number - 1) * page_size
)。 - 每次分页查询需单独执行总记录数查询,增加数据库负担。
- 不同数据库的分页语法差异较大(如 Oracle 使用
ROWNUM
,SQL Server 使用OFFSET FETCH
),维护成本高。
三、MyBatis 分页插件:PageHelper 的实战应用
3.1 分页插件的核心思想
分页插件通过拦截 SQL 执行过程,在原始查询语句中自动添加分页逻辑。例如,PageHelper 是 MyBatis 生态中广泛使用的分页插件,其核心原理是通过 AOP(面向切面编程)拦截 SqlSession
的 select
方法,动态修改 SQL 语句并提取总记录数。
3.2 PageHelper 的环境配置
3.2.1 引入依赖
在 Maven 项目中添加依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
3.2.2 启用插件
在 Spring Boot 的主类或配置类中添加注解:
@EnablePageHelper
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3.3 PageHelper 的使用步骤
3.3.1 开启分页
在 Service 层通过 PageHelper.startPage()
方法开启分页:
public Page<User> getUsers(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize); // 设置当前页和每页数量
List<User> users = userMapper.selectAll(); // 执行原查询语句
return PageInfo.of(users); // 将 List 转换为 Page 对象
}
3.3.2 获取分页结果
通过 PageInfo
类获取分页信息:
PageInfo<User> pageInfo = new PageInfo<>(users);
int total = pageInfo.getTotal(); // 总记录数
int pages = pageInfo.getPages(); // 总页数
List<User> list = pageInfo.getList(); // 当前页的数据
3.4 PageHelper 的优势
- 简化代码:无需手动拼接 SQL 分页语句,只需一行
PageHelper.startPage()
。 - 自动计算总记录数:插件会自动统计总记录数,避免重复查询。
- 数据库兼容性:支持多种数据库(如 MySQL、Oracle 等)的分页语法适配。
3.5 注意事项
- 分页逻辑需在查询前开启:
PageHelper.startPage()
必须在Mapper
方法执行前调用。 - 线程安全:PageHelper 是线程非安全的,需确保在同一个线程内使用。
- 避免嵌套分页:多个分页查询需确保
startPage
的调用顺序正确。
四、自定义分页逻辑:灵活应对复杂场景
4.1 场景需求
当业务需求复杂时(如多表关联分页、动态排序),可能需要自定义分页逻辑。例如,查询用户及其订单的分页数据:
4.2 自定义分页的实现步骤
4.2.1 查询总记录数
public int countUsersWithOrders() {
return userMapper.countUsersWithOrders();
}
4.2.2 查询分页数据
public List<UserWithOrders> getUsersWithOrders(int offset, int pageSize) {
return userMapper.selectUsersWithOrders(offset, pageSize);
}
4.2.3 XML 映射文件
<!-- 总记录数查询 -->
<select id="countUsersWithOrders" resultType="int">
SELECT COUNT(*) FROM user
LEFT JOIN orders ON user.id = orders.user_id
</select>
<!-- 分页数据查询 -->
<select id="selectUsersWithOrders" resultType="UserWithOrders">
SELECT u.*, o.order_id, o.amount
FROM user u
LEFT JOIN orders o ON u.id = o.user_id
ORDER BY u.id
LIMIT #{pageSize} OFFSET #{offset}
</select>
4.3 自定义分页的优缺点
优点:
- 可完全控制 SQL 语句,适合复杂查询场景。
- 兼容性高,不受插件限制。
缺点:
- 代码冗余度较高,需要手动处理分页参数和总记录数。
- 维护成本较大,尤其是多表关联时。
五、分页性能优化与最佳实践
5.1 避免全表扫描
分页查询时,若未添加 ORDER BY
子句或索引不命中,数据库可能执行全表扫描,导致性能下降。例如:
-- 不推荐:未指定排序字段
SELECT * FROM user LIMIT 10 OFFSET 1000;
-- 推荐:指定索引字段排序
SELECT * FROM user ORDER BY id LIMIT 10 OFFSET 1000;
5.2 合理设置分页参数
- 每页数量:根据业务场景设置合理值(如默认 10 或 20 条)。
- 最大页码限制:避免用户输入过大的页码(如 10000 页),可通过
PageHelper
的pageSize
参数控制。
5.3 使用缓存策略
对高频查询的分页数据,可结合 MyBatis 缓存或 Redis 缓存总记录数和部分数据,减少数据库压力。
六、常见问题与解决方案
6.1 分页结果为空页
原因:页码超过总页数。
解决方案:在业务逻辑中校验页码范围,或使用 PageInfo
的 isHasNextPage()
方法。
6.2 分页插件失效
可能原因:
- 未正确配置插件依赖或启动类。
- 分页方法未在同一个线程内调用。
解决方案:检查依赖版本,确保PageHelper.startPage()
在查询前调用。
6.3 总记录数与分页数据不一致
原因:分页查询与总记录数查询的条件不一致(如未关联过滤条件)。
解决方案:确保两个查询的 WHERE
条件完全一致。
结论
分页功能是 MyBatis 开发中不可或缺的技术点,无论是通过原生 SQL、分页插件还是自定义逻辑,开发者需根据具体场景选择最优方案。本文通过案例与代码示例,系统讲解了 mybatis 分页的核心原理与实现方法,帮助开发者快速构建高效、可靠的分页系统。在实际开发中,建议优先使用分页插件(如 PageHelper)简化开发,同时结合性能优化策略,确保系统在高并发场景下的稳定运行。