Mybatis分页功能
MyBatis分页插件
MyBatis作为一个应用广泛的优秀的ORM开源框架,它提供了非常灵活而且功能强大的插件机制。MyBatis允许开发人员在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截Executor、ParameterHandler、ResultSetHandler、StatementHandler所涉及的方法。MyBatis插件使用十分简单,只需实现 Interceptor 接口并指定想要拦截的方法签名即可。
分页基本原理
在数据库操作中,我们通常使用limit offset,size实现分页查询;其中,offset表示偏移量、size表示每页数据量。在此,通过一个小案例梳理分页查询中的核心要点。
假设数据总量total为37,每页数据量pageSize为6。由此,我们可以计算出总页数pages的值为7。当查询第一页的数据时关键语句为limit 0 , 6;当查询第二页的数据时关键语句为limit 6 , 6;当查询第三页的数据时关键语句为limit 12 , 6……以此类推,当查询第n页时分页语句为limit pageSize*(n-1) ,pageSize。
PageHelper核心API
开启分页查询
调用PageHelper.startPage(int pageNum, int pageSize)方法可开启分页查询。调用该方法需要两个输入参数,第一个参数pageNum 表示将查询第几页的数据,第二个参数pageSize 表示每页的数据条数。
PageInfo构造函数
PageInfo是PageHelper的核心类,该类封装了与分页相关的信息。可调用构造函数PageInfo(List<T> list, int navigatePages)创建PageInfo对象。该构造函数第一个参数list表示查询结果,第二个参数表示导航页数量。
PageInfo主要属性
PageInfo是PageHelper的常用类,该类主要属性及其作用如表所示。
属性 |
释义 |
total |
数据总条数 |
pages |
总页数 |
pageSize |
每页数据条数 |
navigatepageNums |
存储所有导航页码的数组 |
navigateFirstPage |
导航起始页码 |
navigateLastPage |
导航终止页码 |
pageNum |
当前页面 |
isFirstPage |
当前页是否是第一页 |
isLastPage |
当前页是否是最后一页 |
hasPreviousPage |
当前页是否有上一页 |
hasNextPage |
当前页是否有下一页 |
prePage |
当前页上一页的页码 |
nextPage |
当前页下一页的页码 |
size |
当前页数据的条数 |
startRow |
当前页第一个元素在数据库中的行号 |
endRow |
当前页最后一个元素在数据库中的行号 |
list |
查询结果集 |
PageHelper使用方法
第一步,导入PageHelper所需依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
第二步,在全局配置文件mybatis-config.xm设置分页插件:
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
第三步,利用PageHelper.startPage( )方法开启分页功能。
PageHelper案例详解
案例开发准备
该表中id字段为主键、name字段表示手机名称、price字段表示手机价格。
-- 创建手机表
DROP TABLE IF EXISTS phone;
CREATE TABLE phone(
id INT PRIMARY KEY auto_increment,
name VARCHAR(50) NOT NULL,
price INT
);
-- 向手机表添加数据
INSERT INTO phone(name,price) VALUES('xiaomi01',3100);
INSERT INTO phone(name,price) VALUES('xiaomi02',3200);
INSERT INTO phone(name,price) VALUES('xiaomi03',3300);
INSERT INTO phone(name,price) VALUES('xiaomi04',3400);
INSERT INTO phone(name,price) VALUES('xiaomi05',3500);
INSERT INTO phone(name,price) VALUES('xiaomi06',3600);
INSERT INTO phone(name,price) VALUES('xiaomi07',3700);
INSERT INTO phone(name,price) VALUES('xiaomi08',3800);
INSERT INTO phone(name,price) VALUES('xiaomi09',3900);
INSERT INTO phone(name,price) VALUES('xiaomi10',4000);
INSERT INTO phone(name,price) VALUES('huawei01',4100);
INSERT INTO phone(name,price) VALUES('huawei02',4200);
INSERT INTO phone(name,price) VALUES('huawei03',4300);
INSERT INTO phone(name,price) VALUES('huawei04',4400);
INSERT INTO phone(name,price) VALUES('huawei05',4500);
INSERT INTO phone(name,price) VALUES('huawei06',4600);
INSERT INTO phone(name,price) VALUES('huawei07',4700);
INSERT INTO phone(name,price) VALUES('huawei08',4800);
INSERT INTO phone(name,price) VALUES('huawei09',4900);
INSERT INTO phone(name,price) VALUES('huawei10',5000);
INSERT INTO phone(name,price) VALUES('realme01',1100);
INSERT INTO phone(name,price) VALUES('realme02',1200);
INSERT INTO phone(name,price) VALUES('realme03',1300);
INSERT INTO phone(name,price) VALUES('realme04',1400);
INSERT INTO phone(name,price) VALUES('realme05',1500);
INSERT INTO phone(name,price) VALUES('realme06',1600);
INSERT INTO phone(name,price) VALUES('realme07',1700);
INSERT INTO phone(name,price) VALUES('realme08',1800);
INSERT INTO phone(name,price) VALUES('realme09',1900);
INSERT INTO phone(name,price) VALUES('realme10',2000);
INSERT INTO phone(name,price) VALUES('newman01',1100);
INSERT INTO phone(name,price) VALUES('newman02',1200);
INSERT INTO phone(name,price) VALUES('newman03',1300);
INSERT INTO phone(name,price) VALUES('newman04',1400);
INSERT INTO phone(name,price) VALUES('newman05',1500);
INSERT INTO phone(name,price) VALUES('newman06',1600);
INSERT INTO phone(name,price) VALUES('newman07',1700);
-- 查询手机表数据
SELECT * FROM phone;
Phone类:
public class Phone {
private int id;
private String name;
private int price;
//省略构造函数、各属性的set和get方法、toString方法
}
接口文件
public interface PhoneMapper {
List<Phone> queryAllPhone();
}
映射文件
<mapper namespace="com.cn.mapper.PhoneMapper">
<select id="queryAllPhone" resultType="Phone">
select * from phone
</select>
</mapper>
测试代码
在测试程序中开启分页功能并进行分页查询,指定查询第4页数据,每页6条数据。分页查询完成后PageHelper利用PageInfo封装了查询结果。PageInfo非常强大且友好,它封装了与分页有关的所有信息。
public class MyBatisTest {
@Test
public void testQueryAllPhone(){
SqlSession sqlSession = null;
try {
// 获取SqlSession
sqlSession = SqlSessionUtil.getSqlSession();
// 获取PhoneMapper
PhoneMapper phoneMapper = sqlSession.getMapper(PhoneMapper.class);
// 开启分页功能。每页6条数据,查询第4页的数据。
PageHelper.startPage(4, 6);
// 执行分页查询
List<Phone> list = phoneMapper.queryAllPhone();
// 打印查询结果
for(Phone phone:list){
System.out.println(phone);
}
// 创建分页,导航条中有5个导航页面
PageInfo<Phone> pageInfo = new PageInfo<>(list,5);
// 获取分页详情
long total = pageInfo.getTotal();
System.out.println("数据总条数: " + total);
int pages = pageInfo.getPages();
System.out.println("总页数: " + pages);
int pageSize = pageInfo.getPageSize();
System.out.println("每页数据条数: " + pageSize);
int navigatePages = pageInfo.getNavigatePages();
System.out.println("导航页数量:"+navigatePages);
int[] navigatePageNums = pageInfo.getNavigatepageNums();
System.out.println("所有导航页码:"+ Arrays.toString(navigatePageNums));
int navigateFirstPage = pageInfo.getNavigateFirstPage();
System.out.println("导航起始页码: " + navigateFirstPage);
int navigateLastPage = pageInfo.getNavigateLastPage();
System.out.println("导航终止页码: " + navigateLastPage);
int pageNum = pageInfo.getPageNum();
System.out.println("当前页码: " + pageNum);
boolean isFirstPage = pageInfo.isIsFirstPage();
System.out.println("当前页是否是第一页: " + isFirstPage);
boolean isLastPage = pageInfo.isIsLastPage();
System.out.println("当前页是否是最后一页: " + isLastPage);
boolean hasPreviousPage = pageInfo.isHasPreviousPage();
System.out.println("当前页是否有上一页: " + hasPreviousPage);
boolean hasNextPage = pageInfo.isHasNextPage();
System.out.println("当前页是否有下一页: " + hasNextPage);
int prePage = pageInfo.getPrePage();
System.out.println("当前页上一页的页码:" + prePage);
int nextPage = pageInfo.getNextPage();
System.out.println("当前页下一页的页码:" + nextPage);
int size = pageInfo.getSize();
System.out.println("当前页数据的条数: " + size);
long startRow = pageInfo.getStartRow();
System.out.println("当前页第一个元素在数据库中的行号: " + startRow);
long endRow = pageInfo.getEndRow();
System.out.println("当前页最后一个元素在数据库中的行号: " + endRow);
// 获取当前页数据
List<Phone> phoneList = pageInfo.getList();
System.out.println("当前页每条数据如下:");
// 打印当前页数据
for(Phone phone:phoneList){
System.out.println(phone);
}
} catch (Exception e) {
System.out.println(e);
} finally {
SqlSessionUtil.closeSqlSession(sqlSession);
}
}
}
从PageInfo中我们可以获取到数据总条数、总页数、每页数据条数、所有导航页码、导航起始页码、导航终止页码、当前页面等等与分页相关的信息。
测试结果
分页执行流程以及查询结果如下:
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Checked out connection 1931444790 from pool.
DEBUG [main] - ==> Preparing: SELECT count(0) FROM phone
DEBUG [main] - ==> Parameters:
DEBUG [main] - <== Total: 1
DEBUG [main] - ==> Preparing: select * from phone LIMIT ?, ?
DEBUG [main] - ==> Parameters: 18(Long), 6(Integer)
DEBUG [main] - <== Total: 6
Phone{id=19, name='huawei09', price=4900}
Phone{id=20, name='huawei10', price=5000}
Phone{id=21, name='realme01', price=1100}
Phone{id=22, name='realme02', price=1200}
Phone{id=23, name='realme03', price=1300}
Phone{id=24, name='realme04', price=1400}
数据总条数: 37
总页数: 7
每页数据条数: 6
导航页数量:5
所有导航页码:[2, 3, 4, 5, 6]
导航起始页码: 2
导航终止页码: 6
当前页码: 4
当前页是否是第一页: false
当前页是否是最后一页: false
当前页是否有上一页: true
当前页是否有下一页: true
当前页上一页的页码:3
当前页下一页的页码:5
当前页数据的条数: 6
当前页第一个元素在数据表中的行号: 19
当前页最后一个元素在数据表中的行号: 24
当前页每条数据如下:
Phone{id=19, name='huawei09', price=4900}
Phone{id=20, name='huawei10', price=5000}
Phone{id=21, name='realme01', price=1100}
Phone{id=22, name='realme02', price=1200}
Phone{id=23, name='realme03', price=1300}
Phone{id=24, name='realme04', price=1400}
PageInfo源码剖析
在稍前的案例中我们知道PageInfo类中的属性navigatepageNums 是一个用于存储所有导航页码的数组。那么,PageInfo究竟是如何根据分页信息计算出数组中的页码呢?为了解开这个疑惑,我们尝试着从PageInfo源码中的calcNavigatepageNums( )方法一探究竟。在该方法中pages表示总页数,navigatePages表示导航页码,pageNum表示当前页码,startNum表示导航的起始页码,endNum表示导航的终止页码。
private void calcNavigatepageNums() {
//当总页数小于或等于导航页码数时
if (pages <= navigatePages) {
navigatepageNums = new int[pages];
for (int i = 0; i < pages; i++) {
navigatepageNums[i] = i + 1;
}
} else { //当总页数大于导航页码数时
navigatepageNums = new int[navigatePages];
int startNum = pageNum - navigatePages / 2;
int endNum = pageNum + navigatePages / 2;
if (startNum < 1) {
startNum = 1;
//最前navigatePages页
for (int i = 0; i < navigatePages; i++) {
navigatepageNums[i] = startNum++;
}
} else if (endNum > pages) {
endNum = pages;
//最后navigatePages页
for (int i = navigatePages - 1; i >= 0; i--) {
navigatepageNums[i] = endNum--;
}
} else {
//所有中间页
for (int i = 0; i < navigatePages; i++) {
navigatepageNums[i] = startNum++;
}
}
}
}
结合刚才的案例我们来分析这一段不算复杂的计算过程。在案例中总页数pages为7,导航条目为5,查询的页码为4。经过计算导航的起始页码startNum=4-5/2最终值为2,导航的终止页码endNum=4+5/2最终值为6。所以,navigatepageNums数组中的导航条目为2,3,4,5,6。