一、分页
这里的分页是指浏览器看到的分页,和操作系统的内存分页完全不一样
什么是分页?
分页(Pagination)是将一个大的数据集划分为多个小的部分(页面),每个页面包含一定数量的记录。分页的目的是使用户能够方便地查看数据集中的一部分,而不是一次性加载和展示整个数据集。
举个例子,假设有一个包含 1000 条用户数据的数据库表。当用户需要查看这些数据时,如果将所有数据一次性加载到页面上,可能会导致:
- 加载时间过长:加载大量数据会消耗大量时间,给用户带来较差的体验。
- 浏览效率低:用户不需要查看所有 1000 条记录,他们可能只关心其中的一部分数据。
- 内存压力大:一次性加载大量数据可能导致服务器内存占用过高,影响系统性能。
因此,通过分页机制,只显示部分记录(如每页显示 20 条记录),让用户可以逐页浏览。
分页的例子
假设有 100 条数据,我们希望每页显示 20 条记录。通过分页机制,数据会被分成 5 页,每一页显示 20 条记录,如下所示:
页面编号 | 数据范围 | 数据显示 |
---|---|---|
第 1 页 | 1 ~ 20 | 显示前 20 条记录 |
第 2 页 | 21 ~ 40 | 显示第 21 到 40 条记录 |
第 3 页 | 41 ~ 60 | 显示第 41 到 60 条记录 |
第 4 页 | 61 ~ 80 | 显示第 61 到 80 条记录 |
第 5 页 | 81 ~ 100 | 显示第 81 到 100 条记录 |
用户可以通过点击 “上一页” 或 “下一页” 来浏览不同的页面,而无需一次性加载所有 100 条数据。
为什么要分页?
-
提升性能和用户体验:
- 减少加载时间:分页可以减少每次请求的数据量,避免一次性加载大量数据导致的性能问题。
- 提高响应速度:分页查询能快速返回一定范围内的数据,避免了等待大量数据加载的时间。
-
节省资源:
- 内存管理:如果一次性加载所有数据,可能会占用大量内存。而分页加载时,每次只处理一部分数据,减少了内存消耗。
- 网络带宽:分页可以减少每次请求的传输数据量,节省网络带宽。
-
改善用户体验:
- 避免信息过载:用户一次性看到所有数据可能会感到信息过载,分页让用户能够按需查看数据。
- 便于定位和浏览:分页将数据拆分为多个页面,用户可以快速浏览并定位到他们感兴趣的数据,提升浏览效率。
-
提升可维护性:
- 对于庞大的数据集,分页能让后台系统更容易管理和维护。一次性查询大量数据可能导致系统崩溃或变得无法响应,而分页查询可以逐步加载数据,保证系统稳定性。
小结
分页的主要目的是优化性能、提升用户体验,并有效管理资源。在面对大量数据时,分页使得我们能够更高效地展示、加载和浏览数据。
二、MyBatis 中的分页方法
MyBatis 可以通过以下几种方式来实现分页:
1. 手动分页
手动分页是指在 SQL 查询中直接使用数据库提供的分页功能。不同的数据库支持不同的分页语法。
示例:
-
MySQL 中使用
LIMIT
和OFFSET
:SELECT * FROM employees LIMIT 10 OFFSET 20;
这条查询语句会从第 21 条记录开始,返回 10 条记录。
-
Oracle 中使用
ROWNUM
或者ROW_NUMBER()
:SELECT * FROM (SELECT a.*, ROWNUM rnumFROM (SELECT * FROM employees) aWHERE ROWNUM <= 30 ) WHERE rnum > 20;
-
在 MyBatis 中,通过 XML 配置或注解直接编写上述 SQL 语句,即可实现分页。在 Mapper 中的示例:
<select id="selectEmployees" resultType="Employee">SELECT * FROM employees LIMIT #{limit} OFFSET #{offset} </select>
调用时,传入
limit
和offset
参数即可实现分页。
2. 使用 RowBounds 进行分页
MyBatis 提供了 RowBounds
类来支持分页。RowBounds
是一个逻辑分页机制,它并不改变 SQL 语句,而是在查询结果集中进行截取。这种方式适用于数据量较小的情况,因为它会先将所有数据查询出来,然后再进行分页。
示例:
RowBounds rowBounds = new RowBounds(offset, limit);
List<Employee> employees = sqlSession.selectList("selectEmployees", null, rowBounds);
虽然使用 RowBounds
实现了分页,但由于它是内存分页,性能较差,因此不推荐在大数据量时使用。
二、分页插件的原理
为了更高效和便捷地实现分页,MyBatis 社区开发了多种分页插件。这些插件通过拦截器(Interceptor)机制,在 SQL 执行前或执行后自动修改 SQL 语句或处理查询结果,实现数据库层面的分页。
1. 分页插件的工作原理
分页插件的工作原理主要包括以下步骤:
-
拦截器机制:分页插件通过 MyBatis 的拦截器机制拦截执行 SQL 的方法,例如
Executor
接口中的query
方法。通过拦截器,插件可以在 SQL 执行之前或执行之后对 SQL 语句进行修改。 -
自动添加分页语句:当拦截到查询方法时,分页插件会检测传入的参数是否包含分页信息(如
pageNum
和pageSize
)。如果包含,插件会根据数据库类型自动为原始 SQL 语句添加相应的分页语句(如LIMIT
、OFFSET
、ROWNUM
等)。 -
执行分页 SQL:经过插件修改的 SQL 会被执行器执行,数据库返回分页后的结果集。
-
封装结果:插件可以进一步封装查询结果,将其封装为分页对象,如
Page<T>
,以便开发者方便地使用分页结果。
2. 常见的分页插件
- PageHelper:PageHelper 是 MyBatis 中最流行的分页插件。它通过自动拦截查询语句,添加
LIMIT
和OFFSET
子句来实现分页,并且能够自动处理分页参数和结果集封装。
1. PageHelper 插件简介
PageHelper 是一个常用的 MyBatis 分页插件,它可以帮助你在 MyBatis 查询中轻松地实现分页功能。通过 PageHelper,分页逻辑无需自己手动处理,插件会自动在 SQL 查询中加入分页参数(如 LIMIT
和 OFFSET
),从而简化分页操作。
2. PageHelper 插件的工作原理
PageHelper 插件的核心原理是在 MyBatis 执行查询前,通过拦截器自动修改 SQL 查询语句,加入分页参数。这些参数是由调用者在调用方法时传入的(如页码、每页条数等)。具体步骤如下:
- 调用
PageHelper.startPage(pageNum, pageSize)
方法时,插件会在后台拦截查询并自动添加LIMIT
和OFFSET
参数。 - 查询语句执行时,PageHelper 会对 SQL 语句进行修改,将分页参数注入其中。
- 执行 SQL 查询后,PageHelper 会自动包装查询结果,返回分页后的数据,同时提供分页信息(如总页数、总记录数等)。
3. 如何集成和使用 PageHelper
3.1 添加 Maven 依赖
首先,需要将 PageHelper
插件添加到你的项目中。可以通过 Maven 来引入 PageHelper 插件的依赖:
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.2.0</version> <!-- 请根据需要选择合适的版本 -->
</dependency>
如果你使用的是 Gradle,可以添加如下依赖:
implementation 'com.github.pagehelper:pagehelper:5.2.0'
3.2 在 MyBatis 配置中启用 PageHelper 插件
在 MyBatis 的配置文件(mybatis-config.xml
)中,启用 PageHelper
插件:
<plugins><plugin interceptor="com.github.pagehelper.PageInterceptor"><property name="dialect" value="mysql" /> <!-- 根据数据库类型设置 --></plugin>
</plugins>
dialect
属性指定了分页插件的方言,MyBatis 的分页插件需要知道当前使用的数据库类型。常见的数据库包括 MySQL、Oracle、PostgreSQL 等。如果使用的是 MySQL,设置为 mysql
。
3.3 在 Mapper 中配置分页查询
你可以在 Mapper 接口中编写分页查询方法,不需要在 SQL 语句中显式地添加分页参数,PageHelper
会自动处理。以下是一个简单的分页查询示例:
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.apache.ibatis.session.SqlSession;public class UserService {private SqlSession sqlSession;public PageInfo<User> getUsersByPage(int pageNum, int pageSize) {// 开始分页PageHelper.startPage(pageNum, pageSize);// 执行查询List<User> userList = sqlSession.selectList("com.example.mapper.UserMapper.selectAllUsers");// 返回分页结果,包括分页信息return new PageInfo<>(userList);}
}
在这个例子中,PageHelper.startPage(pageNum, pageSize)
是分页操作的开始。它会自动将分页参数(页码和每页大小)注入到 SQL 查询中。接下来,调用 sqlSession.selectList
执行查询。查询返回的结果会封装到 PageInfo
对象中,PageInfo
包含了查询结果和分页信息(如总记录数、总页数等)。
3.4 在 XML Mapper 中配置查询语句
Mapper XML 文件中的查询语句通常不需要修改。PageHelper
插件会自动在查询执行前修改 SQL,使其支持分页。例如,下面的查询语句用于查询所有用户:
<select id="selectAllUsers" resultType="com.example.model.User">SELECT * FROM users
</select>
在调用 PageHelper.startPage(pageNum, pageSize)
后,PageHelper
插件会自动为这个查询添加 LIMIT
和 OFFSET
,相应的 SQL 查询会变成:
SELECT * FROM users LIMIT 20 OFFSET 40;
其中,LIMIT 20
表示每页显示 20 条数据,OFFSET 40
表示从第 41 条记录开始查询(即第 3 页的数据,假设每页显示 20 条记录)。
3.5 获取分页信息
使用 PageHelper
进行分页查询时,结果会被自动封装在 PageInfo
对象中。PageInfo
不仅包含查询结果,还包括分页的相关信息,比如总记录数、总页数、当前页等。你可以轻松获取这些信息:
PageInfo<User> pageInfo = new PageInfo<>(userList);// 获取总记录数
long totalRecords = pageInfo.getTotal();// 获取总页数
int totalPages = pageInfo.getPages();// 获取当前页码
int currentPage = pageInfo.getPageNum();
4. PageHelper 的常用配置
PageHelper 提供了一些常用的配置项,帮助你根据需求调整分页行为:
pageSizeZero
: 是否允许分页大小为 0。如果设置为true
,分页时如果pageSize
为 0,PageHelper 会返回所有数据(不分页)。默认为false
。reasonable
: 是否开启分页合理化。如果设置为true
,会对页码和页大小进行合理化处理,比如请求的页码大于最大页数时会自动调整为最大页数。默认为false
。supportMethodsArguments
: 是否支持通过方法参数传递分页参数。如果设置为true
,可以在调用查询方法时直接传递分页参数。
示例配置:
<plugin interceptor="com.github.pagehelper.PageInterceptor"><property name="dialect" value="mysql" /><property name="pageSizeZero" value="true" /><property name="reasonable" value="true" />
</plugin>
5. 总结
使用 PageHelper 插件进行分页的优势在于,它简化了分页的实现过程,不需要手动处理 SQL 查询的 LIMIT
和 OFFSET
参数,只需要在查询之前调用 PageHelper.startPage()
方法即可。分页结果会自动封装在 PageInfo
中,包含分页信息和查询结果,方便前端进行展示。
- 优点:自动处理分页逻辑,减少重复代码,简化开发过程。
- 缺点:插件本身有一定的学习成本,需要根据项目需求合理配置。
通过 PageHelper 插件,你可以非常方便地在 MyBatis 中实现分页功能,提高开发效率和代码可维护性。
References
MyBatis 是如何进行分页的?分页插件的原理是什么?