核心类
我们边开始引用@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 目录结构:
这样就可以 进行访问了;