数据列表的分页实现————分页敏捷开发
概要
分頁功能是比較常見的基礎功能,雖然比較簡單,但是每次需要用到這個功能的時候還是需要現寫一遍。為了實現更加宏觀的業務復用,特將本人特別喜歡的簡易分頁邏輯在此記述,以備日后重用。
邏輯描述
一般的分頁實現方式多是通過SQL語句“LIMIT”子句進行分頁的,如果不清楚LIMIT子句的同學,還請先行了解此子句。
實際上,分頁功能最重要的兩個參數就是pageSize(每頁條數)和pageWant(請求頁碼),這兩個參數都是int型。
pageSize自不必多說,我來說說pageWant,我們常用的“上一頁”“下一頁”、以及“跳到...頁”(當然,對于上一頁和下一頁的情況,頁面需要維護一個全局的當前頁的變量,每次請求后都需要更新這個當前頁的變量,那么再次發起請求的時候,上一頁就是當前頁減一,下一頁就是當前頁加一)都是通過這個參數來請求后端的。這基本解決了前端數據請求的絕大多數情況。另外前端可能需要的幾個重要的數值,比如:總頁數,總條數 都可以由后臺根據相關參數計算生成。
首先,我們可以先定義一個返回值的封裝類,這個類中包含了頁面分頁請求后需要得到的全部數據。
然后,在controller接口的參數列表中,設置int pageSize 和 int pageWant,這里注意,pageSize如果頁面可選,則傳入,如果就固定條數,甚至可以在后臺直接寫死即可。
緊接著,將兩個分頁參數和其他篩選條件一同傳入DAO層,由SQL語句直接進行操作和計算。
最后將返回值封裝為我們一開始定義的封裝類中,直接返回到頁面即可。
功能實現
定義返回Wrapper類型
我們的返回值類型中包含頁面所需的全部信息,包括基本的總頁數,總條數,請求的頁碼,每頁條數,以及最重要的:(經過篩選條件過濾之后的)單頁記錄列表。如下所示:
import java.util.List;public class PageForDataList<T> {/** 每頁條數*/private int pageSize;/** 數據總條數*/private int dataAmount;/** 總頁數*/private int pageAmount;/** 請求頁數*/private int wantPage;/** 單頁記錄列表list*/private List<T> dataList;public PageForDataList() {}public PageForDataList(int pageSize, int dataAmount, int pageAmount, int wantPage, List<T> dataList) {super();this.pageSize = pageSize;this.dataAmount = dataAmount;this.pageAmount = pageAmount;this.wantPage = wantPage;this.dataList = dataList;}public int getPageSize() {return pageSize;}public void setPageSize(int pageSize) {this.pageSize = pageSize;}public int getDataAmount() {return dataAmount;}public void setDataAmount(int dataAmount) {this.dataAmount = dataAmount;}public int getPageAmount() {return pageAmount;}public void setPageAmount(int pageAmount) {this.pageAmount = pageAmount;}public int getWantPage() {return wantPage;}public void setWantPage(int wantPage) {this.wantPage = wantPage;}public List<T> getDataList() {return dataList;}public void setDataList(List<T> dataList) {this.dataList = dataList;} }設置接口參數
根據頁面中的篩選條件的不同,參數有多有少有不同的情況,但是基本都是如下這樣的結構:
????@ApiOperation("獲取特價推薦門票列表,返回值為json結構體")@GetMapping(value = "/special_price/ticket/list")public PageForDataList<SpecialTicketPrice> specialPriceList(@ApiParam(value = "每頁條數") @RequestParam(defaultValue = "10", required = true) int pageSize,@ApiParam(value = "請求頁數") @RequestParam(defaultValue = "1", required = true) int wantPage,@ApiParam(value = "景區名稱") String scenicName, @ApiParam(value = "所在地") String scenicLocation,@ApiParam(value = "推薦狀態") Integer rmdStatus, @ApiParam(value = "折扣起始") Double rateStart,@ApiParam(value = "折扣終止") Double rateEnd) {return sprSvc.ticketList(pageSize, wantPage, scenicName, scenicLocation, rmdStatus, rateStart, rateEnd);}其中,參數列表前兩項為分頁請求的數據,其余全部是篩選條件。sprSvc是一個service。
DAO層的分頁實現
由于我們是通過LIMIT子句來實現分頁功能,因此不論如何,都是要將請求的頁碼傳入SQL來操作的。實際上,Service層在一個簡單的分頁查詢的功能中僅僅充當一個Controller層與DAO層數據交互的傳遞信息的角色。如下service僅供參考:
????@Overridepublic PageForDataList<SpecialTicketPrice> ticketList(int pageSize, int wantPage, String scenicName,String scenicLocation, Integer rmdStatus, Double rateStart, Double rateEnd) {// 直接調用DAO中的查詢SQLPageForDataList<SpecialTicketPrice> pdl = mngDao.findSpecialTicketList(pageSize, wantPage, scenicName,scenicLocation, rmdStatus, rateStart, rateEnd);return pdl;}緊接著DAO層的關鍵實現代碼如下:
????/*** 分頁查詢特價推薦門票列表* <br>作者: mht<br> * 時間:2018年5月7日-上午11:07:51<br>* @return*/public PageForDataList<SpecialTicketPrice> findSpecialTicketList(int pageSize, int wantPage, String scenicName, String scenicLocation, Integer rmdStatus, Double rateStart,Double rateEnd) {StringBuilder sqlBuilder = new StringBuilder("SELECT a.* FROM special_ticket_price a, scenic_sequence b WHERE a.seco_scenic_id = b.seco_scenic_id ");if (scenicName != null && !scenicName.equals("")) {sqlBuilder.append("AND b.scenic_name LIKE '%" + scenicName + "%' ");}if (scenicLocation != null) {sqlBuilder.append("AND a.location = '" + scenicLocation + "' ");}if (rmdStatus != null) {sqlBuilder.append("AND a.rmd_status = " + rmdStatus + " ");}if (rateStart != null) {sqlBuilder.append("AND a.discount_rate >= " + rateStart + " ");}if (rateEnd != null) {sqlBuilder.append("AND a.discount_rate <= " + rateEnd + " ");}// 查詢總條數sqlString countSql = sqlBuilder.toString().replaceAll("a.\\*", "COUNT(*)");sqlBuilder.append("ORDER BY a.seco_product_id LIMIT " + pageSize * (wantPage - 1) + "," + pageSize);// 查詢列表List<SpecialTicketPrice> list = jdbc.query(sqlBuilder.toString(),new BeanPropertyRowMapper<>(SpecialTicketPrice.class));// 查詢dataAmount,數據總條數int dataAmount = jdbc.queryForObject(countSql, int.class);return new PageForDataList<SpecialTicketPrice>(pageSize, dataAmount,(int) Math.ceil(1.0 * dataAmount / pageSize), wantPage, list);}從如上代碼中,我們看到,我建立了一個StringBuilder來處理單線程下的查詢列表的SQL語句sqlBuilder,然后我利用聯表查詢,并將五個參數通過if條件拼接到sqlBuilder后。
這里注意,因為不論是什么系統,分頁查詢一定都是帶著篩選條件之后的分頁數據列表,這個很好理解,比如我們在某寶買衣服,我以“西服”+ "上衣"作為篩選條件,結果分頁之后卻出現了褲子、皮鞋、襯衫、內衣等等,這就完全不符合實際需求。換句話說,分頁功能的實現一定是建立在篩選條件之下的一個功能。
在代碼中,查詢總條數的SQL語句的位置很講究:
// 查詢總條數sql String countSql = sqlBuilder.toString().replaceAll("a.\\*", "COUNT(*)");可以看到,這句SQL是將SELECT子句中的“a.*”替換為了“COUNT(*)”用于查詢符合條件的記錄總條數。而其他條件不變。“\\”則是為了完成“*”的轉義。
為什么說這句SQL的位置講究?
因為它是在篩選條件拼接到sqlBuilder之后才進行總數SQL的變化,這恰恰說明了我剛才提到的,分頁查詢在篩選條件之后的思想。其次,也是非常重要的一點是:countSql的定義,一定要在LIMIT子句之前。換句話說,總數查詢的SQL語句一定不能帶LIMIT子句!稍微一思考就會明白,我們查詢的COUNT(*)應該是符合條件的全部記錄條數,也就是在上述代碼偏后的位置定義的dataAmount變量,如果COUNT查詢在LIMIT子句之后拼入SQL語句(也就是最終得到的是一個帶著LIMIT子句的COUNT查詢),那么我們查詢的結果,也就是記錄總條數dataAmount將始終會小于等于pageSize。不服的同學,可以親自試一試。
( 還要為基礎欠佳的同學補充一點的是,請求分頁的LIMIT表達式應該符合如下公式:
LIMIT pageSize * (wantPage - 1) , pageSize其中,pageSize是從前臺傳入的 1 ,2,3....這樣的正整數。)
然后,我們通過jdbcTemplate來對列表查詢和COUNT查詢的兩條SQL語句進行分別查詢,并賦值給list和dataAmount,從而得到單頁的數據列表和符合條件的總條數。
最后,return的時候,我直接通過最開始定義的返回值封裝類的構造器將我們得到的數據進行封裝返回到controller層。
其中需要通過數學函數 Math.ceil() 求得的總頁數是這樣的:
(int) Math.ceil(1.0 * dataAmount / pageSize)這句話的意思是,用總條數dataAmount除以每頁條數pageSize,然后由于除不盡的原因,我們需要事先將dataAmount變換成浮點型數據,然后這樣我們就可以得到一個double類型的數據,再通過 Math.ceil() 函數,將浮點型數向上取整,再強轉int得到結果。比如,dataAmount = 19,pageSize = 10,那么如上表達式的結果應該是2,也就是總共兩頁。
最后,我們拿到了這樣一個封裝好的數據傳給controller,再通過return,返回給頁面即可。
最終效果展示
由于是測試數據,因此數據并不多,我們可以通過執行的SQL在數據庫中做同樣的查詢看一下結果:
?
綜上,就是對分頁功能的簡單實現。
如有疑問,歡迎文末留言。
?
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的数据列表的分页实现————分页敏捷开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LeetCode算法入门- Merge
- 下一篇: 深入理解Tomcat和Jetty源码之第