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。

posted @ 2023-10-02 21:08  粹白  阅读(98)  评论(0编辑  收藏  举报