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 语句的 LIMITOFFSET 子句实现。其基本语法为:

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(面向切面编程)拦截 SqlSessionselect 方法,动态修改 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 页),可通过 PageHelperpageSize 参数控制。

5.3 使用缓存策略

对高频查询的分页数据,可结合 MyBatis 缓存或 Redis 缓存总记录数和部分数据,减少数据库压力。


六、常见问题与解决方案

6.1 分页结果为空页

原因:页码超过总页数。
解决方案:在业务逻辑中校验页码范围,或使用 PageInfoisHasNextPage() 方法。

6.2 分页插件失效

可能原因

  • 未正确配置插件依赖或启动类。
  • 分页方法未在同一个线程内调用。
    解决方案:检查依赖版本,确保 PageHelper.startPage() 在查询前调用。

6.3 总记录数与分页数据不一致

原因:分页查询与总记录数查询的条件不一致(如未关联过滤条件)。
解决方案:确保两个查询的 WHERE 条件完全一致。


结论

分页功能是 MyBatis 开发中不可或缺的技术点,无论是通过原生 SQL、分页插件还是自定义逻辑,开发者需根据具体场景选择最优方案。本文通过案例与代码示例,系统讲解了 mybatis 分页的核心原理与实现方法,帮助开发者快速构建高效、可靠的分页系统。在实际开发中,建议优先使用分页插件(如 PageHelper)简化开发,同时结合性能优化策略,确保系统在高并发场景下的稳定运行。

最新发布