PageHelper 分页组件

时间:2022-10-07 17:52:44

6.分页组件 pagehelper

6.1.分页是什么

将满足条件的数据, 按一定数量分成多组, 每次只查询其中的一组

每组的数量 页大小 pageSize

每组的编号 页号 pageNum

6.1.1.SQL实现

实现分页的SQL 不同的DBMS各不相同

	-- MySQL:
		SELECT *  FROM  表名称  LIMIT 页大小*(页号-1) ,  页大小
		
	-- Oracle:
		SELECT * 
		FROM 
			(
				SELECT ROWNUM r_u,t1.*  
				FROM  表名称 t1 
				WHERE ROWNUM <= 页大小*(页号-1) + 页大小
			)    t2 
		WHERE  t2.r_u  > 页大小*(页号-1) 
		
	-- SQL Server:
		SELECT TOP 页大小 *
		FROM 表名称
		WHERE id NOT IN
		          (
		          SELECT TOP 页大小*(页号-1) id FROM 表名称 ORDER BY id
		          )
		ORDER BY id

可以发现虽然不同DBMS的SQL完成不同, 但包含 :

从哪条开始 firstRow = 页大小*(页号-1) = pageSize * ( pageNum -1 )

每页最多显示多少条 maxRows = 页大小 = pageSize

6.1.2.页面操作

页面展示数据信息及操作

PageHelper 分页组件

数据包含:

  1. 总记录数 rowCount :
select count(*) rowCount from 表名称
  1. 总页数 pageCount :
pageCount   = rowCount%pageSize==0?rowCount/pageSize:(rowCount/pageSize+1);

操作包含:

  1. 上一页

    pageNum = pageNum - 1>0?pageNum - 1:1;
    
  2. 下一页

    pageNum = pageNum +1>pageCount>?pageCount:( pageNum +1);
    
  3. 第一页(首页) pageNum=1;

  4. 最后一页(尾页) pageNum=pageCount;

6.2.Pagehelper 说明

Pagehelper是基于 Mybatis 框架实现分页组件

Pagehelper与springboot整合地址 : https://github.com/pagehelper/pagehelper-spring-boot

6.2.1.Maven依赖

在 文档中可以找到

 <!--pagehelper分页插件-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.1</version>
</dependency>

如果与 Mybatis plus 整合时

<!--pagehelper分页插件, 屏蔽 mybatis 依赖-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.1</version>
    <exclusions>
        <exclusion>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
        </exclusion>
    </exclusions>
</dependency>

6.2.2.配置文件

这些也可以直接使用默认, 就是不用配置

#标识是数据库方言
pagehelper.helperDialect=mysql
#启用合理化,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页
pagehelper.reasonable=true
#为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值, 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值, 默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero
pagehelper.params=count=countSql
#支持通过 Mapper 接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页
pagehelper.supportMethodsArguments=true
#如果 pageSize=0 就会查询出全部的结果(相当于没有执行分页查询)
pagehelper.pageSizeZero=true

6.2.3.查询代码

封装 分页/条件查询

import lombok.Data;

@Data
public class TabStudentPage {

    // 页号
    private Integer pageNum = 1;
    // 页大小
    private Integer pageSize = 10;

    //stu_name 模糊
    private String stuName;
    //stu_sn 精确
    private String stuSn;
}

在controller类的方法

@RequestMapping("/tabStudent/query")
public String query(TabStudentPage page, ModelMap modelMap){

    System.out.println("page = " + page);
    modelMap.put("page", page);

    PageHelper.startPage(page.getPageNum(), page.getPageSize());
    List<TabStudent> list = tabStudentService.listByPage(page);

    System.out.println("list = " + list);
    // 存储到 request 作用域
    //modelMap.put("list", list);
    modelMap.addAttribute("pageInfo", new PageInfo<TabStudent>(list));
    // 内部转到 列表页 list
    return "tabstudent/list_student";
}

注意点:

  1. PageHelper.startPage(page.getPageNum(), page.getPageSize());

    List<TabStudent> list = tabStudentService.listByPage(page); 这两句之间不能出现异常

  2. 将结果 list 封装成 PageInfo 类对象, 包含了分页所需要的信息

PageInfo{pageNum=1, pageSize=10, size=2, startRow=1, endRow=2, total=2, pages=1, list=Page{count=true, pageNum=1, pageSize=10, startRow=0, endRow=10, total=2, pages=1, reasonable=true, pageSizeZero=true}[SysUser(userId=105, userName=zhangfei, userPass=$2a$10$aUdLHKPBzRotktn7f9AR7eDm9Uvmq0ADgShMohFU9CAhvOHCHrOHq, fullName=张飞, userState=1, userCreatetime=2022-06-29T00:35:35, userCount=2, userLastdate=2022-06-29T00:37:07, userPic=null, remark=null, userSex=1, userTel=13109876543), SysUser(userId=16, userName=admin, userPass=$2a$10$qT0ko9eQEV5ocymMAoh0zONAGqqVRiKb4CLxtlGRNAvT9.sjjqPsC, fullName=管理员, userState=1, userCreatetime=2022-05-30T21:00:42, userCount=662, userLastdate=2022-10-06T15:08:51, userPic=null, remark=管理员, userSex=1, userTel=13900009999)], prePage=0, nextPage=0, isFirstPage=true, isLastPage=true, hasPreviousPage=false, hasNextPage=false, navigatePages=8, navigateFirstPage=1, navigateLastPage=1, navigatepageNums=[1]}
private int pageNum;//当前页码
private int pageSize;//设置每页多少条数据
private int size;//当前页有多少条数据
private int startRow;//当前页码第一条数据的
private int endRow;//当前页码的开始条
private int pages;//当前页码结束条
private int prePage;//上一页(页面链接使用)
private int nextPage;//下一页(页面链接使用)
private boolean isFirstPage;//是否为第一页
private boolean isLastPage;//是否为最后一页
private boolean hasPreviousPage;//是否有前一页
private boolean hasNextPage;//是否有下一页
private int navigatePages;//导航页码数(就是总共有多少页)
private int[] navigatepageNums;//导航页码数(就是总共有多少页),可以用来遍历
private int navigateFirstPage;//首页号
private int navigateLastPage;//尾页号

6.3.页面自定义组件

页面自定义组件 templates/include/page.html

<!DOCTYPE html>
<html lang="zh-cn" xmlns:th="http://www.thymeleaf.org">
<div th:fragment="page(uri,page,formName)">
    <style>
        .zdiv{
            display: inline-block;
            margin-left: 50%;
            transform: translate(-50%);
			font-size: 12px;
        }
        .zpage{
            list-style-type: disc;
            display: -ms-flexbox;
            display: flex;
            padding-left: 0;
            list-style: none;
            border-radius: .25rem;
        }
        .zpage-link:hover {
            z-index: 2;
            color: #0056b3;
            text-decoration: none;
            background-color: #e9ecef;
            border-color: #dee2e6;
        }
        .zpage-link {
            position: relative;
            /* display: block; */
            padding: .5rem .75rem;
            margin-left: -1px;
            line-height: 1.25;
            color: #007bff;
            background-color: #fff;
            border: 1px solid #dee2e6;
            text-decoration:none;
        }
        .zpage-item:first-child .zpage-link {
            margin-left: 0;
            border-top-left-radius: .25rem;
            border-bottom-left-radius: .25rem;
        }
        .zpage-item:last-child .zpage-link {
            border-top-right-radius: .25rem;
            border-bottom-right-radius: .25rem;
        }
        .zpage-item.active .zpage-link {
            z-index: 3;
            color: #fff;
            background-color: #007bff;
            border-color: #007bff;
        }
        .zpage-item.disabled .zpage-link {
            color: #6c757d;
            pointer-events: none;
            cursor: auto;
            background-color: #fff;
            border-color: #dee2e6;
        }
		.zgselect{
			margin: -9px 0px 0px 0px;
			padding: .44rem .25rem;
            border-left: 0;
            border-right: 0;
		}
		.zginput{
			margin: -9px 0px 0px 0px;
			padding: .54rem .25rem;
			text-align: center;
			width: 50px;
		}
		.zgbtn{
			margin-top: -.75rem;
			cursor: pointer;
		}
    </style>
    <div class="zdiv">
        <ul class="zpage">
            <li th:if="${page.isFirstPage}" class="zpage-item disabled"><a class="zpage-link" href="javascript:pagego('1')">首页</a></li>
            <li th:if="${!page.isFirstPage}" class="zpage-item"><a class="zpage-link" href="javascript:pagego('1')">首页</a></li>
            <li th:if="${page.hasPreviousPage}" class="zpage-item"><a class="zpage-link" th:href="|javascript:pagego(${page.prePage})|">上一页</a></li>
            <li th:if="${!page.hasPreviousPage}" class="zpage-item disabled"><a class="zpage-link" th:href="|javascript:pagego(${page.prePage})|">上一页</a></li>
            <li th:if="${page.hasNextPage}" class="zpage-item"><a class="zpage-link" th:href="|javascript:pagego(${page.nextPage})|">下一页</a></li>
            <li th:if="${!page.hasNextPage}" class="zpage-item disabled"><a class="zpage-link" th:href="|javascript:pagego(${page.nextPage})|">下一页</a></li>
            <li th:if="${page.isLastPage}" class="zpage-item disabled"><a class="zpage-link" th:href="|javascript:pagego(${page.pages})|">尾页</a></li>
            <li th:if="${!page.isLastPage}" class="zpage-item"><a class="zpage-link" th:href="|javascript:pagego(${page.pages})|">尾页</a></li>
            <li class="zpage-item"><span class="zpage-link"><span th:text="${page.pageNum}"></span>/<span th:text="${page.pages}"></span></span></li>
            <li class="zpage-item"><span class="zpage-link"><span th:text="${page.total}"></span></span></li>
			<li class="zpage-item">
				<select class="zgselect zpage-link" onchange="setPageSize(this.value)">
					<option value="5" th:selected="${5 eq page.pageSize ? true : false}">5</option>
					<option value="10" th:selected="${10 eq page.pageSize ? true : false}">10</option>
					<option value="20" th:selected="${20 eq page.pageSize ? true : false}">20</option>
					<option value="30" th:selected="${30 eq page.pageSize ? true : false}">30</option>
					<option value="50" th:selected="${50 eq page.pageSize ? true : false}">50</option>
				</select>
			</li>
			<li class="zpage-item">
				<input class="zginput zpage-link" type="text" th:value="${page.pageNum}">
			</li>
			<li class="zpage-item">
<!--				<button class="zgbtn zpage-link" type="button" οnclick="pagego(document.getElementsByClassName('zginput')[0].value)">跳转</button>-->
				<a class="zgbtn zpage-link" href="javascript:pagego(document.getElementsByClassName('zginput')[0].value)">跳转</a>
			</li>
        </ul>
    </div>
    <script type="text/javascript" th:inline="javascript">
        function pagego(shu, hang){
            var formName = [[${formName}]];
            var url = [[${uri}]];
            url = appendPrifix(url);
            var fh = urlForParameter(url);
			var pageSize = hang;
			if(pageSize == null){
				pageSize = [[${page.pageSize}]];
			}
            if (formName != '') {
                createHiddenInput("pageNum",shu,document.getElementsByName(formName)[0])
                createHiddenInput("pageSize",pageSize,document.getElementsByName(formName)[0])
                document.getElementsByName(formName)[0].action = url;
                document.getElementsByName(formName)[0].submit();
            }else{
                var forms = document.getElementsByTagName("form");
                if (forms != null && forms.length > 0) {
                    createHiddenInput("pageNum",shu,forms[0])
                    createHiddenInput("pageSize",pageSize,forms[0])
                    forms[0].action = url;
                    forms[0].submit();
                } else {
                    location.href = url+fh+"pageNum="+shu+"&pageSize="+pageSize;
                }
            }
        }
        function appendPrifix(url){
            return url.substring(0,1)==="/" ? url : "/"+url;
        }
        function urlForParameter(url){
            return url.indexOf("?") > -1 ? '&' : '?';
        }
		function setPageSize(pageSize){
			pagego([[${page.pageNum}]], pageSize);
		}
        //创建隐藏域
        function createHiddenInput(name,value,form){
            var hiddeninput = document.createElement("input");
            hiddeninput.type = "hidden";
            hiddeninput.name = name;
            hiddeninput.value = value;
            form.appendChild(hiddeninput);
        }
    </script>
</div>
</html>

在 列表页面 上使用分页组件

 <div th:replace="include/page::page(uri='/tabStudent/query', page=${pageInfo}, formName='searchForm')"></div>

在 列表的表格中要使用

<tr   th:each="row,i:${pageInfo.list}" >
    <td class="a-center ">
        <input type="checkbox" class="flat table_records"  name="ids" th:value="${row.stuId}" >
    </td>
    <td   th:text="${pageInfo.startRow + i.index}" >121</td>
   	<td th:text="${row.stuName}" >May</td>
    ...
</tr>

6.4.与条件查询结合

条件查询与分页要结合到一起

因为 先 条件查询 , 再点击 分页时, 条件查询的信息不能丢失

条件查询通常是使用表单 form

而 分页通常只传递 页号/页大小 两个信息

所以在 页面自定义组件中 要将两者 整合在一起

6.5.数量特别大时不求总记录数

查询有 200W 以上记录的表时

6.5.1.全查不分页

@RequestMapping("/student/queryAll")
public List<Student> queryAll(){
    // 调用 全查方法
    List<Student> list = studentService.list();
    System.out.println("list = " + list.size());
    // 内部转到 列表页 list
    return list.subList(1, 10);
}

p6spy 监测信息

SQL耗时【4749毫秒】 
最终执行SQL【SELECT  student_id,student_name,student_enrollmenttime,student_tel,education_id,student_weight,student_bloodtype,student_sex  FROM student】
list = 2000020

6.5.2.分页 查看第一页

@RequestMapping("/student/query")
public PageInfo query(StudentPage page){
    System.out.println("page = " + page);
    // 调用 全查方法
    PageHelper.startPage(page.getPageNum(),page.getPageSize());
    List<Student> list = studentService.listForPage(page);
    System.out.println("list = " + list);

    // 内部转到 列表页 list
    return new PageInfo<Student>(list);
}

p6spy 监测信息

page = StudentPage(pageNum=1, pageSize=10, name=null)
SQL耗时【360毫秒】 
最终执行SQL【SELECT count(0) FROM student】
SQL耗时【0毫秒】 
最终执行SQL【select  student_id,student_name,student_enrollmenttime,
                student_tel,education_id,student_weight,
                student_bloodtype,student_sex
        from student LIMIT 10 】

6.5.3.分页 但不查询总记录数

PageHelper.startPage(页号, 页大小 , 是否求总记录数);

@RequestMapping("/student/queryNotCount")
public PageInfo queryNotCount(StudentPage page){
    System.out.println("page = " + page);
    // 调用 全查方法
    PageHelper.startPage(page.getPageNum(),page.getPageSize(), false);
    List<Student> list = studentService.listForPage(page);
    System.out.println("list = " + list);

    // 内部转到 列表页 list
    return new PageInfo<Student>(list);
}

p6spy 监测信息

page = StudentPage(pageNum=1, pageSize=10, name=null)
SQL耗时【1毫秒】 
最终执行SQL【select  student_id,student_name,student_enrollmenttime,
                student_tel,education_id,student_weight,
                student_bloodtype,student_sex
        from student LIMIT 10 】

可以看到明显的时间差

当数据量非常大时,为了保证效率, 一定要用分页, 而且可以不查总记录数