什么是分页?如何使用分页?(二)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 什么是分页?如何使用分页?

核心类

我们边开始引用@Data无效我单独加了get/set 你们按照自己需求来;

package com.aostar.ida.business.datasources.vo;
import java.io.Serializable;
import lombok.Data;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@Data
public class MyPageInfo <T> implements Serializable{
  private static final long serialVersionUID = 1L;
    //当前页
    private Integer pageNum;
    //每页的数量
    private Integer pageSize;
    //当前页的数量
    private int size;
    //当前页面第一个元素在数据库中的行号
    private int startRow;
    //当前页面最后一个元素在数据库中的行号
    private int endRow;
    //总记录数
    private int total;
    //总页数
    private int pages;
    //结果集
    private List<T> list;
    //前一页
    private int prePage;
    //下一页
    private int nextPage;
    //是否为第一页
    private boolean isFirstPage = false;
    //是否为最后一页
    private boolean isLastPage = false;
    //是否有前一页
    private boolean hasPreviousPage = false;
    //是否有下一页
    private boolean hasNextPage = false;
    //导航页码数
    private int navigatePages;
    //所有导航页号
    private int[] navigatepageNums;
    //导航条上的第一页
    private int navigateFirstPage;
    //导航条上的最后一页
    private int navigateLastPage;
    public Integer getPageNum() {
        return pageNum;
    }
    public void setPageNum(Integer pageNum) {
        this.pageNum = pageNum;
    }
    public Integer getPageSize() {
        return pageSize;
    }
    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }
    public int getSize() {
        return size;
    }
    public void setSize(int size) {
        this.size = size;
    }
    public int getStartRow() {
        return startRow;
    }
    public void setStartRow(int startRow) {
        this.startRow = startRow;
    }
    public int getEndRow() {
        return endRow;
    }
    public void setEndRow(int endRow) {
        this.endRow = endRow;
    }
    public int getTotal() {
        return total;
    }
    public void setTotal(int total) {
        this.total = total;
    }
    public int getPages() {
        return pages;
    }
    public void setPages(int pages) {
        this.pages = pages;
    }
    public List<T> getList() {
        return list;
    }
    public void setList(List<T> list) {
        this.list = list;
    }
    public int getPrePage() {
        return prePage;
    }
    public void setPrePage(int prePage) {
        this.prePage = prePage;
    }
    public int getNextPage() {
        return nextPage;
    }
    public void setNextPage(int nextPage) {
        this.nextPage = nextPage;
    }
    public boolean isFirstPage() {
        return isFirstPage;
    }
    public void setFirstPage(boolean firstPage) {
        isFirstPage = firstPage;
    }
    public boolean isLastPage() {
        return isLastPage;
    }
    public void setLastPage(boolean lastPage) {
        isLastPage = lastPage;
    }
    public boolean isHasPreviousPage() {
        return hasPreviousPage;
    }
    public void setHasPreviousPage(boolean hasPreviousPage) {
        this.hasPreviousPage = hasPreviousPage;
    }
    public boolean isHasNextPage() {
        return hasNextPage;
    }
    public void setHasNextPage(boolean hasNextPage) {
        this.hasNextPage = hasNextPage;
    }
    public int getNavigatePages() {
        return navigatePages;
    }
    public void setNavigatePages(int navigatePages) {
        this.navigatePages = navigatePages;
    }
    public int[] getNavigatepageNums() {
        return navigatepageNums;
    }
    public void setNavigatepageNums(int[] navigatepageNums) {
        this.navigatepageNums = navigatepageNums;
    }
    public int getNavigateFirstPage() {
        return navigateFirstPage;
    }
    public void setNavigateFirstPage(int navigateFirstPage) {
        this.navigateFirstPage = navigateFirstPage;
    }
    public int getNavigateLastPage() {
        return navigateLastPage;
    }
    public void setNavigateLastPage(int navigateLastPage) {
        this.navigateLastPage = navigateLastPage;
    }
    /**
     * 对list集合进行分页
     * @param list 需要分页的集合
     * @param pageNum 当前页
     * @param pageSize 每页的数量
     */
    public MyPageInfo(List<T> list,Integer pageNum,Integer pageSize) {
        this(list, 8, pageNum, pageSize);
    }
    /**
     * 对list集合进行分页
     * @param list 需要分页的集合
     * @param navigatePages  导航页码数
     * @param pageNum 当前页
     * @param pageSize 每页的数量
     */
    public MyPageInfo(List<T> list, int navigatePages,int pageNum,int pageSize) {
        if (list instanceof Collection) {
            // 总记录数
            this.total = list.size();
            // 为了跟pageHelper一致,当pageNum<1时,按第一页处理
            int yourPageNum = pageNum;
            pageNum= pageNum<1 ? 1:pageNum;
            // 当前页码
            this.pageNum = pageNum;
            // 每页显示的记录数
            this.pageSize = pageSize;
            // 总页码数
            this.pages = (int)Math.ceil(this.total*1.0/this.pageSize);
            // 导航条页码数
            this.navigatePages = navigatePages;
            // 开始行号
            this.startRow = this.pageNum * this.pageSize - (this.pageSize - 1);
            // 结束行号
            this.endRow = this.pageNum * this.pageSize;
            // 当结束行号>总行数,结束行号=0
            if(this.endRow > this.total){
                if(this.startRow > this.total){
                    this.endRow = 0;
                    this.startRow = 0;
                }else {
                    this.endRow =this.total;
                }
            }
            //计算导航页
            calcNavigatepageNums();
            //计算前后页,第一页,最后一页
            calcPage();
            //判断页面边界
            judgePageBoudary();
            // 当pageNum超过最大页数,则size=0,list为空
            if(this.pageNum > this.pages){
                this.size = 0;
                this.list = Collections.EMPTY_LIST;
            }else {
                this.list = list.subList(startRow - 1,endRow);
                this.size = this.list.size();
            }
            this.pageNum = yourPageNum;
        }
    }
    /**
     * 计算导航页
     */
    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++;
                }
            }
        }
    }
    /**
     * 计算前后页,第一页,最后一页
     */
    private void calcPage() {
        if (navigatepageNums != null && navigatepageNums.length > 0) {
            navigateFirstPage = navigatepageNums[0];
            navigateLastPage = navigatepageNums[navigatepageNums.length - 1];
            if (pageNum > 1) {
                prePage = pageNum - 1;
            }
            if (pageNum < pages) {
                nextPage = pageNum + 1;
            }
        }
    }
    /**
     * 判定页面边界
     */
    private void judgePageBoudary() {
        isFirstPage = pageNum == 1;
        isLastPage = pageNum == pages;
        hasPreviousPage = pageNum > 1;
        hasNextPage = pageNum < pages;
    }
}

👯4.物理分页代码实现;

1. LIMIT用法

LIMIT出现在查询语句的最后,可以使用一个参数或两个参数来限制取出的数据。其中第一个参数代表偏移量:offset(可选参数),第二个参数代表取出的数据条数:rows。

单参数用法

当指定一个参数时,默认省略了偏移量,即偏移量为0,从第一行数据开始取,一共取rows条。

/* 查询前5条数据 */
SELECT * FROM Student LIMIT 5;
双参数用法
当指定两个参数时,需要注意偏移量的取值是从0开始的,此时可以有两种写法:
/* 查询第1-10条数据 */
SELECT * FROM Student LIMIT 0,10;
/* 查询第11-20条数据 */
SELECT * FROM Student LIMIT 10 OFFSET 10;

2. 分页公式

总页数计算

在进行分页之前,我们需要先根据数据总量来得出总页数,这需要用到COUNT函数和向上取整函数CEIL,SQL如下:

/* 获得数据总条数 */
SELECT COUNT(*) FROM Student;
/* 假设每页显示10条,则直接进行除法运算,然后向上取整 */
SELECT CEIL(COUNT(*) / 10) AS pageTotal FROM Student;

核心信息

当前页:pageNumber

每页数据量:pageSize

在实际操作中,我们能够得到的信息有当前所在页以及每页的数据量,同时要注意一下是否超出了最大页数。以每页10条为例,则前三页的数据应为:

第1页:第1~10条,SQL写法:LIMIT 0,10

第2页:第11~20条,SQL写法:LIMIT 10,10

第3页:第21~30条,SQL写法:LIMIT 20,10

据此我们可以总结出,LIMIT所需要的两个参数计算公式如下:

offset:(pageNumber - 1) * pageSize

rows:pageSize

扩展:8种MySQL分页方法总结

方法1: 直接使用数据库提供的SQL语句

语句样式: MySQL中,可用如下方法: 
SELECT * FROM 表名称 LIMIT M,N。

适应场景: 适用于数据量较少的情况(元组百/千级)。

原因/缺点: 全表扫描,速度会很慢 且 有的数据库结果集返回不稳定(如某次返回1,2,3,另外的一次返回2,1,3)。Limit限制的是从结果集的M位置处取出N条输出,其余抛弃。

方法2: 建立主键或唯一索引, 利用索引(假设每页10条)

—语句样式: MySQL中,可用如下方法:

代码如下:

SELECT * FROM 表名称 WHERE id_pk > (pageNum*10) LIMIT M

适应场景: 适用于数据量多的情况(元组数上万)。

—原因: 索引扫描,速度会很快。有朋友提出因为数据查询出来并不是按照pk_id排序的,所以会有漏掉数据的情况,只能方法3。

方法3: 基于索引再排序

—语句样式: MySQL中,可用如下方法: 
SELECT * FROM 表名称 WHERE id_pk > (pageNum*10) ORDER BY id_pk ASC LIMIT M。

适应场景: 适用于数据量多的情况(元组数上万). 最好ORDER BY后的列对象是主键或唯一所以,使得ORDERBY操作能利用索引被消除但结果集是稳定的(稳定的含义,参见方法1)。

原因: 索引扫描,速度会很快. 但MySQL的排序操作,只有ASC没有DESC(DESC是假的,未来会做真正的DESC,期待)。

方法4: 基于索引使用prepare

(第一个问号表示pageNum,第二个?表示每页元组数)

—语句样式: MySQL中,可用如下方法:

代码如下:

PREPARE stmt_name FROM SELECT * FROM 表名称 WHERE id_pk > (?* ?) ORDER BY id_pk
ASC LIMIT M

适应场景: 大数据量。

原因: 索引扫描,速度会很快. prepare语句又比一般的查询语句快点。

方法5:利用MySQL支持ORDER操作可以利用索引快速定位部分元组,避免全表扫描

比如: 读第1000到1019行元组(pk是主键/唯一键)。

代码如下:

SELECT * FROM your_table WHERE pk>=1000 ORDER BY pk ASC LIMIT 0,20

方法6: 利用"子查询/连接+索引"快速定位元组的位置,然后再读取元组. 道理同方法5

如(id是主键/唯一键,蓝色字体时变量):

利用子查询示例:

代码如下:

SELECT* FROMyour_table WHEREid <=
(SELECTid FROMyour_table ORDER
BYid descLIMIT (p a g e − 1 ) ∗ page-1)*page−1)∗pagesize ORDERBYid desc
LIMIT $pagesize

利用连接示例:

代码如下:

SELECT* FROMyour_table ASt1
JOIN(SELECTid FROMyour_table ORDERBY
id descLIMIT (p a g e − 1 ) ∗ page-1)*page−1)∗pagesize ASt2
WHERE
t1.id <= t2.id ORDERBYt1.id descLIMIT $pagesize;

方法7: 存储过程类(最好融合上述方法5/6)

语句样式: 不再给出

适应场景: 大数据量. 作者推荐的方法

原因: 把操作封装在服务器,相对更快一些。

方法8: 反面方法

网上有人写使用 SQL_CALC_FOUND_ROWS。 没有道理,勿模仿 。

基本上,可以推广到所有数据库,道理是一样的。但方法5未必能推广到其他数据库,推广的前提是,其他数据库支持ORDER BY操作可以利用索引直接完成排序。


💼 扩展:Java使用注解实现服务端分页组件

使用一下组件实现通过注解自动服务端分页查询

我们在正常的使用的过程中如果没有太多的业务限制的条件得到情况下我们可以使用mybatis的注解来实现我们的分页的一个展示的情况:

Mybatis-Plus
PageHelper
SpringBoot AOP

使用方法:在controller需要服务端分页的查询接口中使用 @Pagination 标注即可, 如下代码示例:


@Pagination
返回的值是
Result****

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private IUserService userService;
    /**
     * 列出所有用户
     * @return
     */
    @Pagination  
    @GetMapping("/list")
    public Result list(UserQueryReq query) {
        List<User> userList = userService.LisAll();
        return Result.ok(userList);
    }
}

Demo 目录结构:

这样就可以 进行访问了;

此文来自此处;

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
1月前
|
SQL Oracle 关系型数据库
分页
分页
26 1
|
4月前
|
SQL Java 关系型数据库
3.分页
本文介绍了MyBatis中的分页技术,包括四种主要方法:自带`RowBounds`分页、第三方插件PageHelper、SQL分页以及数组分页。`RowBounds`通过内存处理所有查询结果实现分页;PageHelper插件能智能识别数据库类型并自动添加相应的分页关键字;SQL分页直接利用SQL语句中的`LIMIT`或类似关键字;数组分页则是查询所有数据后使用`subList`进行切片。此外,还提到了自定义拦截器实现分页的方式。物理分页虽在小数据量场景下效率较低,但在大数据量时更为适用,优于逻辑分页。
|
7月前
分页实现
分页实现
40 0
|
SQL Oracle 关系型数据库
什么是分页?如何使用分页?(一)
什么是分页?如何使用分页?
189 0
|
7月前
|
SQL Oracle 关系型数据库
3.分页
3.分页
|
SQL 分布式计算 前端开发
分页 fromsize|学习笔记
快速学习分页 fromsize。
分页 fromsize|学习笔记
|
Java 数据库连接 开发者
分页的实现| 学习笔记
快速学习分页的实现
166 0
|
SQL HIVE 开发者
分页 fromsize | 学习笔记
快速学习分页 fromsize
|
SQL Java 数据库连接
分页的实现
分页的实现
|
SQL Java 开发者
分页问题|学习笔记
快速学习 分页问题
104 0