项目亮点之分页设计
分页设计
普通分页
内存分页
内存分页,也叫做逻辑分页。就是从数据库中寻找出所有符合条件的数据记录,然后在逻辑层进行分页操作。
分页请求类
@Data
public class PageReq implements Serializable {
@NotNull(message = "page 不能为空")
private Integer pageNum;
@NotNull(message = "pageSize 不能为空")
private Integer pageSize;
}
分页响应类
@Data
public class PageResp<T> implements Serializable {
private List<T> list;
private Integer totalNum;
private Integer totalPage;
private Integer pageNum;
private Integer pageSize;
}
条件查询类
@Data
public class CommonQueryBean {
private Integer pageSize;
private Integer start;
private String sort;
private String order;
}
条件查询实现
<!-- list4Page 分页查询-->
<select id="list4Page" resultMap="Demo">
SELECT
<include refid="Base_Column_List"/>
from demo
where 1=1
<if test="record.id != null">
and id = #{record.id}
</if>
<if test="record.text != null and record.text != ''">
and text = #{record.text}
</if>
<if test="record.createTime != null">
and create_time = #{record.createTime}
</if>
<if test="record.updateTime != null">
and update_time = #{record.updateTime}
</if>
<if test="record.isDeleted != null">
and is_deleted = #{record.isDeleted}
</if>
<if test="record.isEnabled != null">
and is_enabled = #{record.isEnabled}
</if>
<if test="commonQueryParam != null">
<if test="commonQueryParam.order != null ">
order by #{commonQueryParam.order}
<if test="commonQueryParam.sort != null ">#{commonQueryParam.sort}</if>
</if>
<if test="commonQueryParam.start != null and commonQueryParam.pageSize != null">
limit #{commonQueryParam.start}, #{commonQueryParam.pageSize}
</if>
</if>
</select>
分页逻辑
/**
* 分页边界处理
*
* @param count 总数
* @param pageSize 分页大小
* @return int
* @author 石一歌
* @date 2022/7/14 17:27
*/
public static int getPageCount(int count, int pageSize) {
if (pageSize == 0) {
return 0;
} else {
return count % pageSize == 0 ? count / pageSize : count / pageSize + 1;
}
}
/**
* 首先将分页请求包装类处理,计算得到记录数start和页面大小pageSize并封装到CommonQueryBean
* 执行list4Page,条件查询所有信息(条件查询)和条数
* 调用getPageCount方法,处理当前页和总页数的边界问题,组装结果
*
* @param req 分页请求包装
* @return com.bmw.seed.util.bean.PageResp<com.bmw.seed.model.Demo>
* @author 石一歌
* @date 2022/7/14 16:39
*/
@Override
public PageResp<Demo> page(PageReq req) {
int start = (req.getPageNum() - 1) * req.getPageSize();
Demo param = new Demo();
CommonQueryBean queryBean = new CommonQueryBean();
queryBean.setStart(start);
queryBean.setPageSize(req.getPageSize());
List<Demo> list = demoDao.list4Page(param, queryBean);
int count = demoDao.count(param);
PageResp<Demo> pageResp = new PageResp<>();
pageResp.setList(list);
pageResp.setPageNum(getPageCount(start + 1, req.getPageSize()));
pageResp.setPageSize(req.getPageSize());
pageResp.setTotalNum(count);
pageResp.setTotalPage(getPageCount(count, req.getPageSize()));
return pageResp;
}
物理分页
物理分页,主要是依靠SQL里的
Limit
语法实现的。
核心sql
SELECT * from user limit #{start},#{pageSize}
大数据分页优化
当原始数据的量达到一定量级,那么两种分页方式的效率都会比较低。
- 对于内存分页而言,每次都是取全量数据,上万的数据就需要查询至少几十秒,无法优化。
- 对于物理分页而言,由于是依赖数据库分页,所以可以利用数据库相关优化手段,提高查询效率。
索引覆盖优化
先用快速的索引覆盖查询查出所需主键,再根据主键查询所需要字段
select * from demo where id > =(select id from demo limit 866613, 1) limit 20
select * from demo a join (select id from product demo 866613, 20) b ON a.ID = b.id
游标分页
使用场景
流式展示的分页场景居多,比如朋友圈,今日头条新闻列表等;
需求特征是分页不会取指定页数据,而只需要取下一页数据即可,一般会分页数据按某个维度或者标识排序,比如时间戳或者连续id。
游标的选择
可区分并且连续的字段,比如时间戳或者id。
游标分页逻辑详解
每次请求的参数都包含一个游标值cursor,用来告诉我们上次分页的最后一条数据位置;我们根据游标值,取之后的size条数据返回即可。
分页请求类
@Data
public class CursorPageReq implements Serializable {
@NotNull(message = "pageSize 不能为空")
private Integer pageSize;
private Long cursor;
}
分页响应类
@Data
public class CursorPageResp<T> implements Serializable {
private List<T> list;
private Long cursor;
}
分页逻辑
@Override
public CursorPageResp<UserInfo> cursorPage(CursorPageReq req) {
CursorPageResp<UserInfo> resp = new CursorPageResp<>();
if(req.getCursor()==null){
List<UserInfo> totalList = userInfoDao.list(new UserInfo());
totalList.sort((x,y)->Integer.compare(Math.toIntExact(y.getId()), Math.toIntExact(x.getId())));
List<UserInfo> resultList = totalList.subList(0, req.getPageSize() - 1);
resp.setCursor(Long.valueOf(resultList.get(resultList.size()-1).getId()));
resp.setList(resultList);
return resp;
}
List<UserInfo> list = userInfoDao.list4CursorPage(req);
if (list.size()>0){
resp.setList(list);
resp.setCursor(Long.valueOf(list.get(list.size()-1).getId()));
}
return resp;
}
查询实现
<select id="list4CursorPage" resultMap="UserInfo">
SELECT
<include refid="Base_column_List" />
from user_info
<where>
<if test="req.cursor != null">
and `id` < #{req.cursor}
</if>
</where>
order by `id` desc
limit #{req.pagesize}
</select>