分页是 Web 应用开发中的高频需求,而在 MyBatis 的生态中,<font color="red">MyBatis-Plus 分页插件
PaginationInnerInterceptor</font> 和 MyBatis 的PageHelper是两种常见的实现方案。本文将通过 工作机制、使用方法 和 细节剖析,带你循序渐进地掌握这两种方式,并为你的项目选择提供指导。
# 一、什么是分页?
分页的核心目标是减少数据传输量和前端渲染压力。通过限制每次查询的结果数量,分页能够显著提升用户体验。
假设数据库表中有 100 万条数据,一次性查询所有数据并展示显然不合理。而分页查询可以按需获取,比如:
- 每页显示 10 条数据
- 用户可自由跳转页码
# 二、MyBatis-Plus 分页插件
MyBatis-Plus 内置分页插件 PaginationInnerInterceptor ,提供了简单高效的分页功能。
# 1. 工作机制
- 拦截查询:分页插件通过拦截 SQL 查询,在执行前修改 SQL,自动添加分页条件,例如
LIMIT和OFFSET。 - 查询总记录数:插件会为每次分页查询生成两条 SQL:
- 第一条:查询分页数据
- 第二条:查询总记录数(
SELECT COUNT(*))
- 封装结果:返回的数据会封装到
IPage对象中,包括分页数据、总记录数、总页数等。
# 2. IPage 和 Page 的介绍
在 MyBatis-Plus 的分页功能中, IPage 是分页结果的通用接口,而 Page 是其实现类,主要用于封装分页请求和结果。
# ① 两者的关系
-
IPage是一个接口,定义了分页结果的基本结构。 -
Page是IPage的实现类,既可以用来作为分页请求参数,也可以作为查询结果的载体。
# ② IPage 接口方法
IPage 提供了多个分页相关的抽象方法,常用方法包括:
| 方法 | 描述 |
|---|---|
getRecords() |
获取当前页的数据记录列表 |
getTotal() |
获取总记录数 |
getPages() |
获取总页数 |
getCurrent() |
获取当前页码 |
getSize() |
获取每页记录数 |
setRecords(List<T> records) |
设置当前页数据 |
setTotal(long total) |
设置总记录数 |
# ③ Page 的主要属性
Page 继承了 IPage ,并实现了所有方法,同时增加了一些属性:
# 核心属性
| 属性 | 类型 | 描述 |
|---|---|---|
current |
long |
当前页码 |
size |
long |
每页显示的记录数 |
total |
long |
总记录数 |
pages |
long |
总页数 |
records |
List<T> |
当前页的记录数据列表 |
orders |
List<OrderItem> |
排序规则 |
optimizeCountSql |
boolean |
是否优化 Count 查询 |
searchCount |
boolean |
是否查询总记录数 |
# ④ 示例:如何使用 Page 和 IPage
# 查询示例
// 创建分页请求对象 | |
Page<User> pageRequest = new Page<>(1, 10); | |
// 执行分页查询 | |
IPage<User> result = userMapper.selectPage(pageRequest, null); | |
// 获取分页信息 | |
System.out.println("当前页:" + result.getCurrent()); | |
System.out.println("每页大小:" + result.getSize()); | |
System.out.println("总记录数:" + result.getTotal()); | |
System.out.println("总页数:" + result.getPages()); | |
System.out.println("当前页数据:" + result.getRecords()); |
# 属性扩展
通过 Page 对象,可以轻松获取或设置分页请求和结果:
// 设置排序规则 | |
pageRequest.setOrders(List.of(OrderItem.asc("name"), OrderItem.desc("create_time"))); | |
// 获取排序规则 | |
System.out.println("排序规则:" + pageRequest.getOrders()); |
OrderItem是 MyBatis-Plus 提供的一个 排序条件封装类,用于表示查询中的排序规则。可以通过它为分页查询添加多字段排序,并指定升序或降序。
这里就不展开来讲述了,这个在开发中用的不多,因为可以通过
SQL语句中来指定排序规则通常,这个类的应用场景就是简化代码和与分页功能更好的结合使用,朋友们如果想了解的话可以评论区留言,我后续再补充这部分讲解!
# 3. 配置分页插件
# ① 添加依赖
如果项目中已经引入了 MyBatis-Plus,只需确保版本支持分页即可。以下为 Maven 依赖:
<dependency> | |
<groupId>com.baomidou</groupId> | |
<artifactId>mybatis-plus-boot-starter</artifactId> | |
<version>3.5.3</version> | |
</dependency> |
# ② 启用分页插件
在配置类中添加分页插件:
@Configuration | |
public class MyBatisPlusConfig { | |
@Bean | |
public MybatisPlusInterceptor mybatisPlusInterceptor() { | |
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); | |
interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); | |
return interceptor; | |
} | |
} |
# 4. 使用方法
# ① 分页查询
MyBatis-Plus 提供了 Page 对象,作为分页参数和结果的载体:
public void queryWithMPPage() { | |
// 创建分页对象(当前页,每页条数) | |
Page<User> page = new Page<>(1, 10); | |
// 调用分页查询方法 | |
IPage<User> result = userMapper.selectPage(page, null); | |
// 打印分页结果 | |
System.out.println("总记录数:" + result.getTotal()); | |
System.out.println("总页数:" + result.getPages()); | |
result.getRecords().forEach(System.out::println); | |
} |
# ② 自定义条件查询
结合 QueryWrapper 添加条件:
QueryWrapper<User> queryWrapper = new QueryWrapper<>(); | |
queryWrapper.eq("status", 1).like("name", "Tom"); | |
Page<User> page = new Page<>(1, 5); | |
IPage<User> result = userMapper.selectPage(page, queryWrapper); |
# 5. 复杂 SQL 配合 XML
确实!结合 XML 使用分页功能是实际开发中常见的一种需求,尤其在需要处理复杂 SQL 时。以下是对这一部分内容的详细讲解:
在一些复杂查询场景下,分页通常需要配合手写 SQL 来完成,例如多表联查、动态条件等。MyBatis-Plus 支持在 Mapper 方法中传入 Page 参数,并在 XML 中书写具体的 SQL。
# ① 步骤详解
-
定义 Mapper 方法:在
Mapper接口中定义一个带分页参数的方法:List<User> selectUsers(Page<User> page, @Param("name") String name);
- 第一个参数:
Page对象,MyBatis-Plus 自动识别它为分页参数。 - 其他参数:可以传入查询条件,如这里的
name。
- 第一个参数:
-
在 XML 中书写分页 SQL:在对应的
Mapper.xml文件中书写 SQL,示例如下:<select id="selectUsers" resultType="User">SELECT *
FROM user
WHERE name LIKE CONCAT('%', #{name}, '%')</select>注意:这里没有
LIMIT语句,MyBatis-Plus 会自动在 SQL 上拼接分页参数。 -
调用 Mapper 方法:在服务层调用时传入
Page和其他查询条件:Page<User> page = new Page<>(1, 10); // 页码 1,每页 10 条
List<User> users = userMapper.selectUsers(page, "Tom");
// 获取分页数据List<User> records = page.getRecords();
System.out.println("总记录数:" + page.getTotal());
为什么能实现分页?是否拦截了方法?没错,MyBatis-Plus 是通过 插件机制 实现分页功能的,分页的核心在于 MyBatis 插件拦截器。
# ② 工作机制
1、拦截器原理:MyBatis 提供了插件机制,允许开发者拦截以下四种方法:
Executor的query方法(用于查询操作)。Executor的update方法(用于更新操作)。- 其他如
prepare、parameterize等。
MyBatis-Plus 的分页功能就是通过 拦截 Executor.query 方法,在 SQL 执行之前对其进行修改。
2、执行过程
- 当 Mapper 方法被调用时,MyBatis 会生成对应的 SQL。
- 插件会在 SQL 执行之前,判断是否存在
Page参数。 - 如果存在,插件会:
- 自动生成
COUNT查询,用于获取总记录数。 - 自动为 SQL 添加分页参数(如
LIMIT和OFFSET)。
- 自动生成
- 插件会将分页结果和总记录数封装回
Page对象。
3、XML 适配:因为 Mapper 中的方法和 XML 是一一对应的,所以只要方法参数中包含了 Page 对象,MyBatis-Plus 的分页插件就能捕获并处理。对于开发者来说,这个过程完全透明,无需手动拼接分页逻辑。
# ③ 示例扩展:复杂 SQL 的分页
假如有一个需要多表联查的场景:
<select id="selectUserOrders" resultType="UserOrderDTO"> | |
SELECT u.id, u.name, o.order_id, o.amount | |
FROM user u | |
LEFT JOIN orders o ON u.id = o.user_id | |
WHERE u.name LIKE CONCAT('%', #{name}, '%') | |
</select> |
# 调用
Page<UserOrderDTO> page = new Page<>(1, 10); // 每页 10 条 | |
List<UserOrderDTO> userOrders = userMapper.selectUserOrders(page, "Tom"); | |
// 获取分页结果 | |
System.out.println("总记录数:" + page.getTotal()); | |
page.getRecords().forEach(System.out::println); |
# 插件自动生成的 SQL
-
查询总记录数:
SELECT COUNT(*)
FROM user u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.name LIKE '%Tom%';
-
查询分页数据:
SELECT u.id, u.name, o.order_id, o.amount
FROM user u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.name LIKE '%Tom%'
LIMIT 0, 10;
【小结一下】
通过 XML 和 MyBatis-Plus 的分页插件结合,可以轻松实现复杂 SQL 的分页功能:
- 不需要手动拼接分页逻辑。
- 依赖 MyBatis 插件机制,分页参数的拼接完全透明。
- 支持动态传入分页条件和查询参数,灵活性极高。
这种方式既保留了
XML的灵活性,又简化了分页逻辑,开发效率和代码可读性大幅提升
# 6. PaginationInnerInterceptor 优势与特点
| 特点 | 说明 |
|---|---|
| 无侵入性 | 基于插件实现,无需修改原有方法 |
| 功能全面 | 支持条件查询、排序等功能 |
| 与 MP 无缝衔接 | 自动适配 MP 的其他功能(如逻辑删除、乐观锁) |
| 性能优化 | 插件内置了多种优化策略,如防止分页过大导致的性能问题 |
# 三、 PageHelper 分页
PageHelper 是专为 MyBatis 设计的分页插件,功能强大且适配性广。
# 1. 工作机制
- 动态注入分页参数:使用
PageHelper.startPage方法时,会动态生成分页条件。 - 拦截执行 SQL:与 MyBatis-Plus 类似,PageHelper 会修改 SQL,添加分页语句。
- 结果封装:返回的结果由
PageInfo对象封装,包含分页数据、总记录数等。
# 2. 配置 PageHelper
# ① 添加依赖
<dependency> | |
<groupId>com.github.pagehelper</groupId> | |
<artifactId>pagehelper-spring-boot-starter</artifactId> | |
<version>1.4.6</version> | |
</dependency> |
# ② 配置文件中启用 PageHelper
pagehelper: | |
helper-dialect: mysql | |
reasonable: true | |
support-methods-arguments: true | |
params: count=countSql |
# 3. 使用方法
# ① 分页查询
在执行查询前调用 PageHelper.startPage :
public void queryWithPageHelper() { | |
// 设置分页参数(页码,页面大小) | |
PageHelper.startPage(1, 10); | |
// 调用普通查询方法 | |
List<User> users = userMapper.selectAll(); | |
// 封装分页结果 | |
PageInfo<User> pageInfo = new PageInfo<>(users); | |
System.out.println("总记录数:" + pageInfo.getTotal()); | |
System.out.println("当前页数据:" + pageInfo.getList()); | |
} |
# ② 条件查询
PageHelper 本身不提供条件构造器,但可以结合自定义 SQL 使用:
PageHelper.startPage(1, 5); | |
List<User> users = userMapper.queryWithConditions("Tom", 1); | |
PageInfo<User> pageInfo = new PageInfo<>(users); |
# 4. 优势与特点
| 特点 | 说明 |
|---|---|
| 适配性强 | 可用于任何 MyBatis 项目,无需依赖 MyBatis-Plus |
| 支持复杂查询 | 可以结合自定义 SQL 或复杂逻辑 |
| 灵活性高 | 可动态设置分页参数 |
| 侵入性较低 | 不改变原有 Mapper 方法 |
# 四、两种分页方式对比
| 对比项 | PaginationInnerInterceptor |
PageHelper |
|---|---|---|
| 集成难度 | 简单,MyBatis-Plus 自带分页功能 | 需额外引入依赖 |
| 分页参数 | 使用 Page 对象,封装清晰 |
动态调用 startPage ,侵入性稍高 |
| 适用场景 | 推荐使用 MyBatis-Plus 项目 | 推荐在普通 MyBatis 项目中使用 |
| 扩展性 | 更适合 MyBatis-Plus,支持动态条件构造 | 适用于多种场景,支持多表复杂查询 |
| 性能优化 | 内置多种优化策略 | 无内置优化机制 |
# 五、总结与选择建议
-
PaginationInnerInterceptor:如果你的项目已经使用了 MyBatis-Plus,分页插件是更推荐的选择,它能够与 MP 的其他功能无缝配合,简单高效。 -
PageHelper:如果你的项目是普通 MyBatis 项目,PageHelper 是一个成熟且灵活的解决方案,支持复杂场景。
