在使用Mysql實現分頁時,前端一般傳遞分頁參數給後端,後端在把分頁列表數據給前端進行展示。這思想沒問題。都是這個套路,根據不同的問題,編寫不同的代碼。
傳統分頁就是在數據基本不會變化時,就是不會有新數據插入進來,前端一般 是傳遞 頁碼,每一頁的數量,代碼如下
@Data
public class PageEntity implements Serializable {
//頁碼
public Integer page;
//每一頁數量
public Integer limit;
//模糊查詢的變量
public String condition;
}
@RequestMapping("/productList")
public ResponseData list(PageEntity pageEntity) {
if (pageEntity == null) {
pageEntity = new PageEntity();
pageEntity.setPage(1);
pageEntity.setLimit(10);
}
int shopId = 1;
PageUtils pageUtils = this.productService.WxListProductPage(shopId, pageEntity);
return ResponseData.success(pageUtils);
}
@Override
public PageUtils WxListProductPage(int shopId, PageEntity pageEntity) {
QueryWrapper<Product> queryWrapper = new QueryWrapper<>();
if (StringUtils.isNotEmpty(pageEntity.condition)) {
queryWrapper.like("product_name", pageEntity.condition);
}
queryWrapper.eq("shop_id", shopId);
queryWrapper.eq("product_state", "ENABLE");
//上面是需要對數據進行篩選
//下面是分頁參數的計算
int count = this.baseMapper.selectCount(queryWrapper);
//Mysql的Limit的第一個值
int start = (pageEntity.getPage() - 1) * pageEntity.getLimit();
//Mysql的Limit的第二個值
int num = pageEntity.getLimit();
//調用Mapper查詢數據獲取返回結果
List<Map<String, Object>> mapList = this.baseMapper.queryWxListProduct(shopId, start, num, pageEntity.getCondition());
return new PageUtils(mapList, count, pageEntity.getLimit(), pageEntity.getPage());
}
<select id="queryWxListProduct" resultType="map">
SELECT
a.id AS id,
c.title AS categoryName,
a.product_name AS productName,
a.num AS num,
a.price AS price,
a.product_desc AS productDesc,
a.create_time AS createTime,
a.update_time AS updateTime,
a.enable_point AS enablePoint,
a.point AS point,
b.file_name AS fileName
FROM product a
LEFT JOIN sys_file_info b ON a.img = b.file_id
LEFT JOIN product_category c ON a.product_category_id = c.id
WHERE
a.shop_id = #{shopId}
AND a.product_state = 'ENABLE'
<if test="condition != null and condition != ''">
AND product_name like CONCAT('%',#{condition},'%')
</if>
ORDER BY
a.create_time DESC
LIMIT #{start}, #{num}
</select>
上面就是傳統分頁的實現過程,我這是真實項目裏面的一部分代碼,所以有很多對與本次演示不相關的代碼。
而實際情況是後端數據庫裏面會對我要查詢的這張表會經常有新數據產生,而在產生新數據後新的數據會影響到我們分頁的查詢結果,在查詢結果中會出現重複數據,例如下圖
第一頁的數據
第二頁的數據
上述的內容第一頁的最後一條出現在了第二頁的第一條,就是因爲數據庫新插入了一條數據,而這條數據影響了我們後臺分頁的請求參數的計算。
因此,爲了不影響用戶體驗,減少Bug,提高產品質量,我們需要修改代碼,然新產生的數據不會對本階段的分頁數據產生影響,需要獲取新產生的數據需要刷新從第一頁開始取值就可以。每當我開始請求第二頁時就不要出現重複數據了。
動態分頁的實現過程如下
/**
* 在分頁時過濾新插入的數據
* @param headerId 第一次請求下列表中最大的ID
* @param nextId 每一次請求下列表中最小的ID
* @param limit 每一頁需要多少條
* @param page 頁碼,後端無意義
* @return
*/
@RequestMapping("/productListById")
public ResponseData listById(Integer headerId, Integer nextId, Integer limit, Integer page) {
if (limit == null) {
limit = 10;
}
if (page == null) {
page = 1;
}
int shopId = 1;
PageUtils pageUtils = this.productService.WxListProductPageById(shopId, headerId, nextId, limit, page);
return ResponseData.success(pageUtils);
}
@Override
public PageUtils WxListProductPageById(Integer shopId, Integer headerId, Integer nextId,Integer limit,Integer page) {
//一、篩選ID小於等於第一次查詢的最大ID的數量
//二、篩選指定店鋪的數據
//三、篩選不被禁用的商品
QueryWrapper<Product> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("shop_id", shopId);
queryWrapper.eq("product_state", "ENABLE");
if (headerId != null && headerId > 0) {
queryWrapper.le("id", headerId);
}
int count = this.baseMapper.selectCount(queryWrapper);
List<Map<String, Object>> mapList = this.baseMapper.queryWxListProductById(shopId, 0, limit, nextId);
return new PageUtils(mapList, count, limit, page);
}
<select id="queryWxListProductById" resultType="map">
SELECT
a.id AS id,
c.title AS categoryName,
a.product_name AS productName,
a.num AS num,
a.price AS price,
a.product_desc AS productDesc,
a.create_time AS createTime,
a.update_time AS updateTime,
a.enable_point AS enablePoint,
a.point AS point,
b.file_name AS fileName
FROM
product a
LEFT JOIN sys_file_info b ON a.img = b.file_id
LEFT JOIN product_category c ON a.product_category_id = c.id
WHERE
a.shop_id = #{shopId}
AND a.product_state = 'ENABLE'
<if test="nextId != null and nextId != ''">
AND a.id < #{nextId}
</if>
ORDER BY a.id DESC
LIMIT #{start}, #{num}
</select>
在上面動態分頁代碼實現中,最重要的參數就是nextId,我們通過nextId去判斷下一頁的Id應該從哪裏開始,我們既然通過了nextId來篩選數據,那麼在LIMIT使用就要發生變化,LIMIT的第一個值就一直是0,表示從篩選的數據開始第一個開始取分頁數據,因爲我們通過nextId過濾了數據,所以不需要第一個值發生改變了,LIMIT的第二個值是需要取的條目數量,在前面我們還有一個headerId,這個參數是第一次請求的時候,獲取列表中最大的Id,每一次請求的時候就可以通過這個Id取小於這個Id的條目數量,保證每一次返回的總數量,頁碼不會發生改變。
我這裏使用的是自增長ID,因此我通過ID可以實現,也可以設計字段插入時間,通過插入時間排序後,比較時間段來篩選數據也可以。
最後,貼一下PageUtils類的代碼
/**
* 分頁工具類
*
*/
public class PageUtils implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 總記錄數
*/
private int totalCount;
/**
* 每頁記錄數
*/
private int pageSize;
/**
* 總頁數
*/
private int totalPage;
/**
* 當前頁數
*/
private int currPage;
/**
* 列表數據
*/
private List<?> list;
/**
* 分頁
*
* @param list 列表數據
* @param totalCount 總記錄數
* @param pageSize 每頁記錄數
* @param currPage 當前頁數
*/
public PageUtils(List<?> list, int totalCount, int pageSize, int currPage) {
this.list = list;
this.totalCount = totalCount;
this.pageSize = pageSize;
this.currPage = currPage;
this.totalPage = (int) Math.ceil((double) totalCount / pageSize);
}
/**
* 分頁
*/
public PageUtils(IPage<?> page) {
this.list = page.getRecords();
this.totalCount = (int) page.getTotal();
this.pageSize = (int) page.getSize();
this.currPage = (int) page.getCurrent();
this.totalPage = (int) page.getPages();
}
public int getTotalCount() {
return totalCount;
}
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public int getTotalPage() {
return totalPage;
}
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
public int getCurrPage() {
return currPage;
}
public void setCurrPage(int currPage) {
this.currPage = currPage;
}
public List<?> getList() {
return list;
}
public void setList(List<?> list) {
this.list = list;
}
}