一款很好用的 分页插件,支持多种数据库,拿来即用
springboot 2.7.1、 jdk11、pagehelper1.4.2
<!-- 版本需要1.4 以及以上 否则会报错 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.1</version>
</dependency>
启动报错: 使用1.4 以下版本,会报如下错
解决办法:二选一即可,推荐第一种
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.1</version>
</dependency>
spring:
main:
allow-circular-references: true
// pageNum 当前页数, pageSize 每页条数;
/*
注意:
1. 只有紧跟在PageHelper.startPage方法后的第一个Mybatis的查询(Select)方法会被分页。
2. 由于嵌套结果方式会导致结果集被折叠,因此分页查询的结果在折叠后总数会减少,所以无法保证分页结果数量正确。
*/
PageHelper.startPage(pageNum, pageSize);
List<SysUser1> list1 = sysUser1Mapper.example();
// 方式1
PageHelper.startPage(1, 1);
List<SysUser1> list1 = sysUser1Mapper.example();
System.out.println("方式1: " + list1);
System.out.println();
// 方式二
PageHelper.offsetPage(1, 1);
List<SysUser1> list2 = sysUser1Mapper.example();
System.out.println("方式2: " + list2);
System.out.println();
// 方式三
List<SysUser1> list3 = session.selectList("com.example.demoproject.server.dao.SysUser1Mapper.list", null, new PageRowBounds(1, 1));
System.out.println("方式3: " + list3);
System.out.println();
/*
方式四,参数方法调用
1. 接口方法定义, xml 中不需要处理这两个参数 List selectByPageNumSize(@Param("pageNum") int pageNum, @Param("pageSize") int pageSize);
2. supportMethodsArguments=true
*/
List<SysUser1> list4 = sysUser1Mapper.selectByPageNumSize(1, 1);
System.out.println("方式4: " + list4);
System.out.println();
/*
第五种
1. 参数对象 com.example.demoproject.server.common.req.BaseReq 属性有 pageNum 和 pageSize 只要参数有值,也会被分页
2. supportMethodsArguments=true
*/
BaseReq baseReq = new BaseReq();
baseReq.setPageNum(1);
baseReq.setPageSize(1);
List<SysUser1> list5 = sysUser1Mapper.selectByPageNumSize(baseReq);
System.out.println("方式5: " + list5);
System.out.println();
/*
第六种,ISelect 接口方式; 也可以 lambda表达式方式
6.1 doSelectPage
6.2 doSelectPageInfo
6.3 PageHelper.count
*/
// 6.1 doSelectPage
Page<SysUser1> page = PageHelper.startPage(1, 1).doSelectPage(new ISelect() {
@Override
public void doSelect() {
List<SysUser1> list6 = sysUser1Mapper.example();
System.out.println("方式6-doSelectPage-1: " + list6);
System.out.println();
}
});
System.out.println("方式6-doSelectPage-2: " + page);
System.out.println();
// 6.2 doSelectPageInfo
PageInfo<SysUser1> objectPageInfo = PageHelper.startPage(1, 1).doSelectPageInfo(new ISelect() {
@Override
public void doSelect() {
List<SysUser1> list6 = sysUser1Mapper.example();
System.out.println("方式6-doSelectPageInfo-1: " + list6);
System.out.println();
}
});
System.out.println("方式6-doSelectPageInfo-2: " + objectPageInfo);
System.out.println();
// 6.3 PageHelper.count
long total = PageHelper.count(new ISelect() {
@Override
public void doSelect() {
List<SysUser1> list6 = sysUser1Mapper.example();
System.out.println("方式6-count-1: " + list6);
System.out.println();
}
});
System.out.println("方式6-total-2: " + total);
System.out.println();
#格式:
# pagehelper:
# xxxxx: xx
# 如:
# pagehelper:
# offsetAsPageNum: false
# ############################常用配置####################################################
# helperDialect 数据库方言, 默认:分页插件会自动检测当前的数据库链接,自动选择合适的分页方式
# 可选择 oracle,mysql,postgresql,db2,sqlserver,sqlserver2012 等
# 特别注意: 使用 SqlServer2012 数据库时, 需要手动指定为 sqlserver2012, 否则会使用 SqlServer2005 的方式进行分页
# offsetAsPageNum:默认值为 false, 该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为 true 时,会将 RowBounds 中的 offset 参数当成 pageNum 使用,可以用页码和页面大小两个参数进行分页。
# rowBoundsWithCount:默认值为false,该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为true时,使用 RowBounds 分页会进行 count 查询。
# pageSizeZero:默认值为 false,当该参数设置为 true 时,如果 pageSize=0 或者 RowBounds.limit = 0 就会查询出全部的结果(相当于没有执行分页查询,但是返回结果仍然是 Page 类型)。
# reasonable:分页合理化参数,默认值为false。当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询。
# params:为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值, 默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero。
# supportMethodsArguments:支持通过 Mapper 接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页
# ############################不常用配置####################################################
# autoRuntimeDialect:默认值为 false。设置为 true 时,允许在运行时根据多数据源自动识别对应方言的分页 (不支持自动选择sqlserver2012,只能使用sqlserver)
# closeConn:默认值为 true。当使用运行时动态数据源或没有设置 helperDialect 属性自动获取数据库类型时,会自动获取一个数据库连接, 通过该属性来设置是否关闭获取的这个连接,默认true关闭,设置为 false 后,不会关闭获取的连接,这个参数的设置要根据自己选择的数据源来决定。
配置项说明: springboot 会自动联想,此处注意如果联想出来不是驼峰命名,也不报错,但是参数会不生效
如:
上述说的简单使用方式,只在要执行的sql之前加一行代码就行,那这行代码是怎么做到的呢?想搞清楚这个首先引出最简单的问题是分页参数怎么和sql执行关联,sql又是什么时候执行的?
我们可以跟踪进去看下发现代码是这样的,分两步,第一步是,分页参数包装为Page对象,第二步是,Page对象放入 ThreadLocal 中。
ThreadLocal 是什么呢,简单说是 本地线程变量,存储了当前线程所需要的数据副本,只有当前线程可以使用,作为解决并发问题的一种实现(spring就是基于此)。也就是说,如果不手动清理的话,你放进去的数据,在线程销毁之前都能用,所以一般用这个的时候不用的数据要及时手动清理防止内存泄漏。
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
// 1.构建page对象,记住这个对象
Page<E> page = new Page<E>(pageNum, pageSize, count);
page.setReasonable(reasonable);
page.setPageSizeZero(pageSizeZero);
//当已经执行过orderBy的时候
Page<E> oldPage = getLocalPage();
if (oldPage != null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
// 2.page对象 放入 ThreadLocal 中
setLocalPage(page);
return page;
}
PageInterceptor.intercept 方法。首先看PageInterceptor这个类,拦截了两个方法,一个是四个参数,一个是六个参数的 实现类 org.apache.ibatis.plugin.Interceptor(mybatis提供的插件类)
再看方法 intercept ,重写了mybatis提供的方法,核心统计代码如下,主要就是 skip方法 有三个实现类 根据不同配置有不同的实现,此处是PageHelper
//调用方法判断是否需要进行分页,如果不需要,直接返回结果
if (!dialect.skip(ms, parameter, rowBounds)) {
//判断是否需要进行 count 查询(有了分页参数才会进来,此处不知道为什么还需要再次判断下)
if (dialect.beforeCount(ms, parameter, rowBounds)) {
//查询总数 此处就是查询核心代码
Long count = count(executor, ms, parameter, rowBounds, null, boundSql);
//处理查询总数,返回 true 时继续分页查询,false 时直接返回
if (!dialect.afterCount(count, parameter, rowBounds)) {
//当查询总数为 0 时,直接返回空的结果
return dialect.afterPage(new ArrayList(), parameter, rowBounds);
}
}
resultList = ExecutorUtil.pageQuery(dialect, executor,
ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
} else {
//rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
PageHelper.skip() 主要是 pageParams.getPage 获取分页信息
@Override
public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
if (ms.getId().endsWith(MSUtils.COUNT)) {
throw new RuntimeException("在系统中发现了多个分页插件,请检查系统配置!");
}
// 获取分页信息
Page page = pageParams.getPage(parameterObject, rowBounds);
if (page == null) {
return true;
} else {
//设置默认的 count 列
if (StringUtil.isEmpty(page.getCountColumn())) {
page.setCountColumn(pageParams.getCountColumn());
}
autoDialect.initDelegateDialect(ms, page.getDialectClass());
return false;
}
}
PageParams.getPage(Object parameterObject, RowBounds rowBounds)重头戏来了,分页处理,处理完回到 skip 方法
public Page getPage(Object parameterObject, RowBounds rowBounds) {
/*
分页方式: PageHelper.startPage 、 RowBound 方式 、 supportMethodsArguments=true
*/
Page page = PageHelper.getLocalPage();
if (page == null) {
// 2.没有主动设置分页 此处支持自己设置 分页参数时的逻辑此处用的是等等不是 equals
if (rowBounds != RowBounds.DEFAULT) {
// 3. rowBounds分页逻辑
if (offsetAsPageNum) {
page = new Page(rowBounds.getOffset(), rowBounds.getLimit(), rowBoundsWithCount);
} else {
page = new Page(new int[]{rowBounds.getOffset(), rowBounds.getLimit()}, rowBoundsWithCount);
//offsetAsPageNum=false的时候,由于PageNum问题,不能使用reasonable,这里会强制为false
page.setReasonable(false);
}
if(rowBounds instanceof PageRowBounds){
PageRowBounds pageRowBounds = (PageRowBounds)rowBounds;
page.setCount(pageRowBounds.getCount() == null || pageRowBounds.getCount());
}
} else if(parameterObject instanceof IPage || supportMethodsArguments){
// IPage 或 supportMethodsArguments 参数配置处理(入参里面拿分页参数封装为page对象)
try {
page = PageObjectUtil.getPageFromObject(parameterObject, false);
} catch (Exception e) {
return null;
}
}
if(page == null){
return null;
}
// 没有显示设置分页参数 且需要分页时 分页参数放入 ThreadLocal
PageHelper.setLocalPage(page);
}
//分页合理化
if (page.getReasonable() == null) {
page.setReasonable(reasonable);
}
//当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果
if (page.getPageSizeZero() == null) {
page.setPageSizeZero(pageSizeZero);
}
return page;
}
分页参数解析后,如果需要分页,则skip 校验通过,进入if逻辑 执行分页sql,然后在查询数据,最终将数据封装为 Page(实际是list)返回,详见 4.3.1 程序入口 注释说明
1. PageHelper是一款国人自己编写的分页插件,结合mybatis、 spring 使用
2. 简单使用方式,只需要在需要执行的sql上面,加入代码 PageHelper.startPage(pageNum, pageSize); 即可实现
3. 分页原理是 通过ThreadLocal 或者 开启自动查找参数中的分页参数,在执行真实sql 之前查询总数,然后返回Page(list)对象,实现分页效果
文中所属项目下载地址: https://download.csdn.net/download/m0_46861007/86248872