赞
踩
一款很好用的 分页插件,支持多种数据库,拿来即用
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<SysUser1> 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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。