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