# MyBatis教程 - 11 分页
当数据库表中的数据很多的时候,查询数据的时候就需要分页。
我们一般在 SQL 中使用 LIMIT 关键字,来限制查询的结果,从而实现分页的功能:
例如:
-- 返回前5条记录
SELECT * FROM tb_user LIMIT 5;
-- 从第6条记录开始,返回5条记录(即第2页,假设每页5条)
SELECT * FROM tb_user LIMIT 5 OFFSET 5;
-- MySQL 特有的语法,等同于上面的查询
SELECT * FROM tb_user LIMIT 5, 5;
2
3
4
5
6
7
8
只需要将参数传递给 LIMIT ? OFFSET ?
就可以实现简单的分页功能了。
但是在实际的开发中,在页面需要封装分页的信息,通常是上面显示一个数据的表格,下面会显示如下的分页信息:
而页面上需要根据分页的信息显示有多少页、当前是第几页、每一页的条数、当前页的条数、总共有多少条、当前是否是第一页(用来判断是否可以点击上一页按钮)、当前是否是最后一页(用来判断是否可以点击下一页按钮)等等信息。
如果我们自己来封装这些信息,会有点麻烦,这个时候我们可以借助第三方的插件来完成。在 MyBatis 中用的比较多的就是 PageHelper (官网:https://pagehelper.github.io/)。
下面来介绍如何使用 PageHelper 插件。
# 11.2 集成PageHelper
首先在项目中集成 PageHelper 。
# 1 添加PageHelper依赖
首先在项目的 pom.xml 中集成 PageHelper 依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>6.1.0</version>
</dependency>
2
3
4
5
添加完成,要 右键 -> Maven -> Reload project
。
# 2 配置PageHelper插件
在 MyBatis 的全局配置文件中,配置PageHelper插件的拦截器。
在 mybatis-config.xml
中配置:
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
</plugin>
</plugins>
2
3
4
5
注意 mybatis-config.xml
中配置项的顺序,<plugins>
要配置在 <environments>
前面。
# 11.2 使用PageHelper
下面看一下如何使用 PageHelper。
# 1 准备数据
首先生成一些数据,用于分页获取:
INSERT INTO `tb_user`
(`username`, `password`, `email`, `age`, `create_time`, `update_time`)
VALUES
('user1', 'pwd12345', 'user1@foooor.com', 25, NOW(), NOW()),
('user2', 'pwd67890', 'user2@foooor.com', 30, NOW(), NOW()),
('user3', 'pwd112233', 'user3@foooor.com', 28, NOW(), NOW()),
('user4', 'pwd445566', 'user4@foooor.com', 35, NOW(), NOW()),
('user5', 'pwd778899', 'user5@foooor.com', 22, NOW(), NOW()),
('user6', 'pwd001122', 'user6@foooor.com', 29, NOW(), NOW()),
('user7', 'pwd334455', 'user7@foooor.com', 34, NOW(), NOW()),
('user8', 'pwd667788', 'user8@foooor.com', 27, NOW(), NOW()),
('user9', 'pwd990011', 'user9@foooor.com', 32, NOW(), NOW()),
('user10', 'pwd223344', 'user10@foooor.com', 24, NOW(), NOW()),
('user11', 'pwd556677', 'user11@foooor.com', 26, NOW(), NOW()),
('user12', 'pwd889900', 'user12@foooor.com', 31, NOW(), NOW()),
('user13', 'pwd11223344', 'user13@foooor.com', 23, NOW(), NOW()),
('user14', 'pwd55667788', 'user14@foooor.com', 36, NOW(), NOW()),
('user15', 'pwd99001122', 'user15@foooor.com', 21, NOW(), NOW()),
('user16', 'pwd33445566', 'user16@foooor.com', 30, NOW(), NOW()),
('user17', 'pwd77889900', 'user17@foooor.com', 25, NOW(), NOW()),
('user18', 'pwd11223355', 'user18@foooor.com', 29, NOW(), NOW()),
('user19', 'pwd55667799', 'user19@foooor.com', 34, NOW(), NOW()),
('user20', 'pwd99001133', 'user20@foooor.com', 28, NOW(), NOW()),
('user21', 'pwd22334466', 'user21@foooor.com', 32, NOW(), NOW()),
('user22', 'pwd66778811', 'user22@foooor.com', 26, NOW(), NOW()),
('user23', 'pwd00112244', 'user23@foooor.com', 31, NOW(), NOW()),
('user24', 'pwd44556677', 'user24@foooor.com', 24, NOW(), NOW()),
('user25', 'pwd88990011', 'user25@foooor.com', 27, NOW(), NOW()),
('user26', 'pwd33445588', 'user26@foooor.com', 35, NOW(), NOW()),
('user27', 'pwd77889922', 'user27@foooor.com', 23, NOW(), NOW()),
('user28', 'pwd11223366', 'user28@foooor.com', 30, NOW(), NOW()),
('user29', 'pwd55667700', 'user29@foooor.com', 25, NOW(), NOW()),
('user30', 'pwd99001144', 'user30@foooor.com', 29, NOW(), NOW()),
('user31', 'pwd22334477', 'user31@foooor.com', 33, NOW(), NOW()),
('user32', 'pwd66778833', 'user32@foooor.com', 26, NOW(), NOW()),
('user33', 'pwd00112255', 'user33@foooor.com', 31, NOW(), NOW()),
('user34', 'pwd44556688', 'user34@foooor.com', 28, NOW(), NOW()),
('user35', 'pwd88990022', 'user35@foooor.com', 32, NOW(), NOW()),
('user36', 'pwd33445599', 'user36@foooor.com', 24, NOW(), NOW()),
('user37', 'pwd77889933', 'user37@foooor.com', 27, NOW(), NOW()),
('user38', 'pwd11223377', 'user38@foooor.com', 35, NOW(), NOW()),
('user39', 'pwd55667711', 'user39@foooor.com', 23, NOW(), NOW()),
('user40', 'pwd99001155', 'user40@foooor.com', 26, NOW(), NOW()),
('user41', 'pwd22334488', 'user41@foooor.com', 30, NOW(), NOW()),
('user42', 'pwd66778844', 'user42@foooor.com', 25, NOW(), NOW()),
('user43', 'pwd00112266', 'user43@foooor.com', 29, NOW(), NOW()),
('user44', 'pwd44556699', 'user44@foooor.com', 34, NOW(), NOW()),
('user45', 'pwd88990033', 'user45@foooor.com', 28, NOW(), NOW());
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 2 编写接口方法
编写接口方法和 Mapper.xml 中的 SQL 映射。
UserMapper.java
/**
* 查询所有用户
*/
List<User> selectAll();
2
3
4
UserMapper.xml
<!-- 定义查询所有用户的 SQL 语句 -->
<select id="selectAll" resultType="User">
SELECT * FROM tb_user
</select>
2
3
4
定义的是查询所有的数据,不需要针对分页做额外的处理,待会 PageHelper 会处理。
# 3 使用PageHelper分页
使用 PageHelper 分页非常的简单,直接编写测试代码:
@Test
public void testSelectByPager() {
// 获取SqlSession连接
SqlSession sqlSession = MyBatisUtils.getSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 开启分页查询,第一个参数为当前页,第二个参数为每页的记录数
PageHelper.startPage(1, 5);
List<User> userList = userMapper.selectAll();
// 获取分页信息
PageInfo pageInfo = new PageInfo(userList);
log.info("userList size: {}", userList.size());
log.info("pageInfo: {}", pageInfo);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
上面在查询方法之前调用 PageHelper.startPage
方法,紧跟在这个方法后的第一个 MyBatis 查询方法 会被进行分页。
所以虽然我们没有对 userMapper.selectAll()
进行分页处理,PageHelper 也会对查询进行拦截,并添加 limit
命令。
执行结果如下,可以看到会有两条执行 SQL,一条是查询总条数,一条是进行分页查询:
PageInfo 包含了非常全面的分页属性,包含了 Page 对象信息,完整的 PageInfo 信息如下:
PageInfo{
pageNum=1,
pageSize=5,
size=5,
startRow=1,
endRow=5,
total=45,
pages=9,
list=Page{
count=true,
pageNum=1,
pageSize=5,
startRow=0,
endRow=5,
total=45,
pages=9,
reasonable=false,
pageSizeZero=false}[User(id=1, username=user1, password=pwd12345, email=user1@foooor.com, age=25, createTime=Tue Aug 27 18:20:26 CST 2024, updateTime=Tue Aug 27 18:20:26 CST 2024), User(id=2, username=user2, password=pwd67890, email=user2@foooor.com, age=30, createTime=Tue Aug 27 18:20:26 CST 2024, updateTime=Tue Aug 27 18:20:26 CST 2024), User(id=3, username=user3, password=pwd112233, email=user3@foooor.com, age=28, createTime=Tue Aug 27 18:20:26 CST 2024, updateTime=Tue Aug 27 18:20:26 CST 2024), User(id=4, username=user4, password=pwd445566, email=user4@foooor.com, age=35, createTime=Tue Aug 27 18:20:26 CST 2024, updateTime=Tue Aug 27 18:20:26 CST 2024), User(id=5, username=user5, password=pwd778899, email=user5@foooor.com, age=22, createTime=Tue Aug 27 18:20:26 CST 2024, updateTime=Tue Aug 27 18:20:26 CST 2024)],
prePage=0,
nextPage=2,
isFirstPage=true,
isLastPage=false,
hasPreviousPage=false,
hasNextPage=true,
navigatePages=8,
navigateFirstPage=1,
navigateLastPage=8,
navigatepageNums=[1, 2, 3, 4, 5, 6, 7, 8]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
PageInfo
包含了分页的所有重要信息,以下是 PageInfo
参数的解释:
pageNum=1
:当前页码,这里是第1页。pageSize=5
:每页显示的记录数,这里是5条。size=5
:当前页实际显示的记录数,这里是5条,与pageSize
相同,表示没有数据被省略。startRow=1
:当前页第一条记录在总结果集中的位置,这里是第1条。endRow=5
:当前页最后一条记录在总结果集中的位置,这里是第5条。total=45
:总记录数,这里是45条。pages=9
:总页数,这里是9页。list
:当前页的数据列表,包含5个User
对象。prePage=0
:前一页的页码,这里是0,表示没有前一页。nextPage=2
:后一页的页码,这里是2,表示下一页是第2页。isFirstPage=true
:是否是第一页,这里是true
。isLastPage=false
:是否是最后一页,这里是false
。hasPreviousPage=false
:是否有前一页,这里是false
。hasNextPage=true
:是否有下一页,这里是true
。navigatePages=8
:导航分页的页码数,这里是8,表示分页导航条显示的页码数量。navigateFirstPage=1
:导航分页条的第一页页码,这里是1。navigateLastPage=8
:导航分页条的最后一页页码,这里是8。navigatepageNums=[1, 2, 3, 4, 5, 6, 7, 8]
:导航分页条中显示的页码列表。
PageInfo
提供了丰富的分页信息,可以很方便地实现分页功能,并且在前端展示分页导航。
# 11.3 分页的原理
当调用 PageHelper.startPage
方法时,它实际上是将分页参数(页码、每页条数等)存储到了一个与当前线程绑定的 ThreadLocal
变量中。ThreadLocal
保证了每个线程的分页参数是隔离的,互不干扰。
PageHelper
是基于 MyBatis 的拦截器(Interceptor)机制,当 MyBatis 执行 SQL 查询时, PageHelper
的拦截器会检测到这个查询。并检查 ThreadLocal
中是否有分页参数。如果检测到分页参数,拦截器会根据这些参数动态地修改原始的 SQL 语句,通常是添加 LIMIT 子句来限制查询结果的数量,以及可能需要一个子查询或计算来确定总记录数。
# 11.4 使用注意点
下面这样的代码,可能会导致出问题:
PageHelper.startPage(1, 10);
List<User> list;
if(param1 != null){
list = userMapper.selectIf(param1);
} else {
list = new ArrayList<User>();
}
2
3
4
5
6
7
上面这种情况下由于 param1 存在 null 的情况,就会导致 PageHelper 生产了一个分页参数,但是没有被消费,这个参数就会一直保留在这个线程上。当这个线程再次被使用时,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页。
上面这个代码,应该写成下面这个样子:
List<User> list;
if(param1 != null){
PageHelper.startPage(1, 10);
list = userMapper.selectIf(param1);
} else {
list = new ArrayList<User>();
}
2
3
4
5
6
7
这种写法就能保证安全。