开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 9 天,点击查看活动详情
我们已经实现了Mybatis的各种操作及各种增删改查等操作, 在实际项目中, 如果需要大批量的数据从数据库中查询出来, 直接会撑爆内存, 导致OOM 场景, 所以我们一些场景是需要分页查询的
1.逻辑分页与物理分页
首先我们弄明白什么是分页 数据库的某个表里有1000条数据,我们每次只显示100条数据,在第1页显示第0到第99条,在第2页显示第100到199条,依次类推,这就是分页。 分页可以分为逻辑分页和物理分页。
逻辑分页
- 逻辑分页就是内存分页 首先查询得到表中的所有的1000条数据, 然后 根据你需要的页码, 拆分出来 对应页的数据, 比如pageSize 每页50 个数据, 就是 20页, 需要取第一页 pageNumber=1 只需要对 1000条数据 做批量拆分 分成20份, 然后成熟根据当前页的“页码pageNumber”选出其中的50条数据来显示。
- 逻辑分页缺点就是需要把数据全部加载到内存中,如果是大批量的数据就会导致OOM, 就别提分页了,适用于小数据处理
物理分页
- 物理分页是程序先判断出该选出这1000条的第几条到第几条,然后在数据库层面 根据分页信息pageNumber和每页大小pageSize 计算出需要的100条返回数据。
- 物理分页是比较常用的,每次都查询 单页的数据,不会造成内存数据过大,OOM的问题
物理分页
下面我们介绍几种 物理分页逻辑
测试类TestController
/**
* 查询user接口
*/
@RequestMapping("/temp/query8")
@ResponseBody
public void query8(Integer pageNumber) {
//pageNumber 默认都是 从第1页开始,每页取3个
Page page = new Page(pageNumber, 3);
List<UserInfoPO> memoryPage = pageService.getPageUser(page);
log.info(" memoryPage 第{}页 数据 \r\n:{} ", page.getPageNumber(), printUser(memoryPage));
//limit 底层从 0 开始 , 第一页 3 个数据, 其实是 limit 0,3
Page pageLimit = new Page(pageNumber - 1, 3);
List<UserInfoPO> limitResult = pageService.pageLimit(pageLimit);
log.info(" limitResult 第{}页 数据 \n:{} ", page.getPageNumber() - 1, printUser(limitResult));
//rowRoundsPage 底层从 0 开始 , 第一页 3 个数据, 其实是 0,3
Page pageRounds = new Page(pageNumber - 1, 3);
List<UserInfoPO> rowRoundsResult = pageService.pageRowRounds(pageRounds);
log.info(" rowRoundsResult 第{}页 数据 \n:{} ", page.getPageNumber(), printUser(rowRoundsResult));
}
private String printUser(List<UserInfoPO> users) {
StringBuffer sb = new StringBuffer();
for (UserInfoPO user : users) {
sb.append(user.toString()).append("\r\n");
}
return sb.toString();
}
1.内存分页
工具类PageUtil 起始页从1页开始
package com.jzj.tdmybatis.util;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Data
public class MyPageUtil<T> implements Serializable {
private static final long serialVersionUID = -8741766802354222579L;
// 每页显示多少条记录
private int pageSize;
// 当前第几页数据 从 1 开始
private int pageNum;
// 一共多少条记录
private int total;
// 一共多少页
private int pages;
// 要显示的数据
private List<T> list = new ArrayList<>();
private MyPageUtil() {
}
public MyPageUtil(int pageSize, int currentPage, int totalRecord, int totalPage, List<T> dataList) {
super();
this.pageSize = pageSize;
this.pageNum = currentPage;
this.total = totalRecord;
this.pages = totalPage;
this.list = dataList;
}
public MyPageUtil(int pageNum, int pageSize, List<T> sourceList) {
if (sourceList == null || sourceList.isEmpty())
return;
// 总记录条数
this.total = sourceList.size();
// 每页显示多少条记录
this.pageSize = pageSize;
// 获取总页数
this.pages = this.total / this.pageSize;
if (this.total % this.pageSize != 0)
this.pages = this.pages + 1;
// 当前第几页数据
this.pageNum = Math.min(this.pages, pageNum);
// 起始索引
int fromIndex = this.pageSize * (this.pageNum - 1);
// 结束索引
int toIndex = Math.min(this.pageSize * this.pageNum, this.total);
this.list = sourceList.subList(fromIndex, toIndex);
}
}
代码逻辑
/**
* 内存分页
*/
List<UserInfoPO> getPageUser(Page page);
@Override
public List<UserInfoPO> getPageUser(Page page) {
Example example = new Example(UserInfoPO.class);
List<UserInfoPO> userInfoPOS = mapper.selectByExample(example);
//从第几页 开始, 每页多少条数据, PageUtil 从1开始数页码
PageUtil<UserInfoPO> pageUtil = new PageUtil<>(page.getPageNumber(), page.getPageSize(), userInfoPOS);
return pageUtil.getList();
}
查询结果
<== Total: 14
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4ab39fc9]
2023-02-10 00:31:56.619 INFO 17936 --- [nio-8800-exec-1] c.j.tdmybatis.controller.TestController : memoryPage 第3页 数据
:UserInfoPO{id=7, userId='77', userName='gg', age=41, address='郑州'}
UserInfoPO{id=8, userId='88', userName='hh', age=4, address='武汉'}
UserInfoPO{id=9, userId='99', userName='ii', age=14, address='武汉'}
可以看到查询了 全部14条数据, 只取了 7,8,9 三条数据
2. limit关键字
直接通过 limit 关键字来实现分页
- 优点:灵活性高
- 缺点:limit 如果页码较大的情况下,效率较低,因为页码较大,它找到游标的位置就比较耗时
代码逻辑
/**
* Limit 分页
*/
List<UserInfoPO> pageLimit(Page page);
===================================
@Override
public List<UserInfoPO> pageLimit(Page page) {
//从第几页 开始, 每页多少条数据, limit SQL层 从0 开始数页码
int start = page.getStartPosition();
return mapper.getUserByLimit(start, page.getPageSize());
}
===================================
@Select(" select * from user_info limit #{start}, #{pageSize} ")
List<UserInfoPO> getUserByLimit(@Param("start") Integer start, @Param("pageSize") Integer pageSize);
查询结果
==> Preparing: select * from user_info limit ?, ?
==> Parameters: 6(Integer), 3(Integer)
<== Columns: id, user_id, user_name, age, address, order_ids, goods, sort_order, is_del, is_del2, addtime, modtime
<== Row: 7, 77, gg, 41, 郑州, <<BLOB>>, <<BLOB>>, 0, 0, 0, 0, 0
<== Row: 8, 88, hh, 4, 武汉, <<BLOB>>, <<BLOB>>, 0, 0, 0, 0, 0
<== Row: 9, 99, ii, 14, 武汉, <<BLOB>>, <<BLOB>>, 0, 0, 0, 0, 0
<== Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@573d25b1]
2023-02-10 00:31:56.624 INFO 17936 --- [nio-8800-exec-1] c.j.tdmybatis.controller.TestController : limitResult 第2页 数据
:UserInfoPO{id=7, userId='null', userName='null', age=41, address='郑州'}
UserInfoPO{id=8, userId='null', userName='null', age=4, address='武汉'}
UserInfoPO{id=9, userId='null', userName='null', age=14, address='武汉'}
查询了3条 数据, 分页也是 7,8,9 三条数据
3.RowBounds 分页
在 mybatis 中,使用 RowBounds 进行分页,非常方便,不需要在 sql 语句中写 limit,即可完成分页功能,适用于返回数据结果较少的查询中使用
重要事情说三遍
!!! 不适合大数据查询
!!! 不适合大数据查询
!!! 不适合大数据查询
由于它是在 sql 查询出所有结果的基础上截取数据的,所以在数据量大的sql中并不适用
代码逻辑
/**
* RowBounds 分页
*/
List<UserInfoPO> pageRowRounds(Page page);
===================================
@Override
public List<UserInfoPO> pageRowRounds(Page page) {
int start = page.getStartPosition();
//从第几页 开始, 每页多少条数据 从0开始数页码
RowBounds rowBounds = new RowBounds(start, page.getPageSize());
return mapper.getUserByRowBounds(rowBounds);
}
===================================
@Select(" select * from user_info")
List<UserInfoPO> getUserByRowBounds(RowBounds rowBounds);
查询结果
<== Row: 1, 11, aa, 1, 北京, <<BLOB>>, <<BLOB>>, 0, 0, 0, 0, 0
<== Row: 2, 22, bb, 2, 上海, <<BLOB>>, <<BLOB>>, 0, 0, 0, 0, 0
<== Row: 3, 33, cc, 31, 广州, <<BLOB>>, <<BLOB>>, 0, 0, 0, 0, 0
<== Row: 4, 44, dd, 14, 深圳, <<BLOB>>, <<BLOB>>, 0, 0, 0, 0, 0
<== Row: 5, 55, ee, 25, 上海, <<BLOB>>, <<BLOB>>, 0, 0, 0, 0, 0
<== Row: 6, 66, ff, 34, 西安, <<BLOB>>, <<BLOB>>, 0, 0, 0, 0, 0
<== Row: 7, 77, gg, 41, 郑州, <<BLOB>>, <<BLOB>>, 0, 0, 0, 0, 0
<== Row: 8, 88, hh, 4, 武汉, <<BLOB>>, <<BLOB>>, 0, 0, 0, 0, 0
<== Row: 9, 99, ii, 14, 武汉, <<BLOB>>, <<BLOB>>, 0, 0, 0, 0, 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@274b6e39]
2023-02-10 00:31:56.626 INFO 17936 --- [nio-8800-exec-1] c.j.tdmybatis.controller.TestController : rowRoundsResult 第3页 数据
:UserInfoPO{id=7, userId='null', userName='null', age=41, address='郑州'}
UserInfoPO{id=8, userId='null', userName='null', age=4, address='武汉'}
UserInfoPO{id=9, userId='null', userName='null', age=14, address='武汉'}
查了 9条数据, 就是本次查询的 最大值9 的数据,然后分页处理 7,8,9 三条数据
至此, 我们用 内存分页, limit分页, RowRounds 分页的逻辑处理完毕