在做某購物商城的系統時,遇到了需要分頁的情況,采用myBatis的一個十分便捷的插件PageHelper進行后台分頁。具體使用方法就是通過maven添加依賴:
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>4.1.0</version>
</dependency>
使用的過程如下:
Controller層:
@RequestMapping("list.do") @ResponseBody /** * 獲取產品詳情 */ public ServerResponse getList(HttpSession session, @RequestParam(value = "pageNum",defaultValue = "1") int pageNum, @RequestParam(value = "pageNum",defaultValue = "10")int pageSize){ //非關鍵代碼省略if(iUserService.checkAdminRole(user).isSuccess()){ return iProductService.getProductList(pageNum, pageSize); } }
Service實現:
public ServerResponse<PageInfo> getProductList(int pageNum,int pageSize){
//startPage
PageHelper.startPage(pageNum, pageSize);
//填充自己的sql,此過程是將拿到的數據庫對象轉換為表示層的VO對象,可忽略
List<ProductListVo> productListVoList = new ArrayList<ProductListVo>();
List<Product> productList = productMapper.selectList();
for(Product productItem : productList){
ProductListVo productListVo = assembleProductListVo(productItem);
productListVoList.add(productListVo);
}
//pageHelper
PageInfo pageResult = new PageInfo(productList);
pageResult.setList(productListVoList);
return ServerResponse.createBySuccess(pageResult);
}
接着我們來了解PageHelper的內部實現過程。
首先來看startPage的設計,進入該靜態方法中,代碼中方法重載的startPage代碼如下:
/** * 開始分頁 * * @param pageNum 頁碼 * @param pageSize 每頁顯示數量 * @param count 是否進行count查詢 * @param reasonable 分頁合理化,null時用默認配置 * @param pageSizeZero true且pageSize=0時返回全部結果不執行分頁,false時分頁,null時用默認配置 */ public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) { Page<E> page = new Page<E>(pageNum, pageSize, count); page.setReasonable(reasonable); page.setPageSizeZero(pageSizeZero); //當已經執行過orderBy的時候 Page<E> oldPage = SqlUtil.getLocalPage(); if (oldPage != null && oldPage.isOrderByOnly()) { page.setOrderBy(oldPage.getOrderBy()); } SqlUtil.setLocalPage(page); return page; }
第一行代碼對應page的構造方法為
public Page(int pageNum, int pageSize, boolean count) { this(pageNum, pageSize, count, null);
}
其中調用的構造方法為:
private Page(int pageNum, int pageSize, boolean count, Boolean reasonable) { super(0); if (pageNum == 1 && pageSize == Integer.MAX_VALUE) { pageSizeZero = true; pageSize = 0; } this.pageNum = pageNum; this.pageSize = pageSize; this.count = count; calculateStartAndEndRow(); setReasonable(reasonable); }
第一行super(0)是調用父類ArrayList的構造函數,接着幾行是對參數的填充,我們先看看 calculateStartAndEndRow() 方法,顧名思義就是計算起始和終止行號,看代碼:
/** * 計算起止行號 */ private void calculateStartAndEndRow() { this.startRow = this.pageNum > 0 ? (this.pageNum - 1) * this.pageSize : 0; this.endRow = this.startRow + this.pageSize * (this.pageNum > 0 ? 1 : 0); }
再看setReasonable(reasonable)的作用,先放代碼:
public Page<E> setReasonable(Boolean reasonable) { if (reasonable == null) { return this; } this.reasonable = reasonable; //分頁合理化,針對不合理的頁碼自動處理 if (this.reasonable && this.pageNum <= 0) { this.pageNum = 1; calculateStartAndEndRow(); } return this; }
顯而易見,這是對表示層傳來的頁數做邏輯處理,頁數如果為0或負數,則置1。其實這些邏輯在前端就需要進行控制,當頁數為1時,無法點擊進行上一頁。
接下來我們再回頭看Service實現中的pageHelper的代碼:
首先是創建一個pageInfo對象,將從數據庫拿到的list數據作為參數,看構造函數代碼:
/** * 包裝Page對象 * * @param list */ public PageInfo(List<T> list) { this(list, 8); }
點進去看:
/** * 包裝Page對象 * * @param list page結果 * @param navigatePages 頁碼數量 */ public PageInfo(List<T> list, int navigatePages) { if (list instanceof Page) { Page page = (Page) list; this.pageNum = page.getPageNum(); this.pageSize = page.getPageSize(); this.orderBy = page.getOrderBy(); this.pages = page.getPages(); this.list = page; this.size = page.size(); this.total = page.getTotal(); //由於結果是>startRow的,所以實際的需要+1 if (this.size == 0) { this.startRow = 0; this.endRow = 0; } else { this.startRow = page.getStartRow() + 1; //計算實際的endRow(最后一頁的時候特殊) this.endRow = this.startRow - 1 + this.size; } } else if (list instanceof Collection) { this.pageNum = 1; this.pageSize = list.size(); this.pages = 1; this.list = list; this.size = list.size(); this.total = list.size(); this.startRow = 0; this.endRow = list.size() > 0 ? list.size() - 1 : 0; } if (list instanceof Collection) { this.navigatePages = navigatePages; //計算導航頁 calcNavigatepageNums(); //計算前后頁,第一頁,最后一頁 calcPage(); //判斷頁面邊界 judgePageBoudary(); } }
在不傳導航頁碼數量的參數時,默認導航頁碼數量為8。首先判斷list類型,如果是page類型,填參即可,注意startRow和endRow的計算方法。如果是Collection類型,也是進行填參,然后執行三個方法分別是calcNavigatepageNums(); calcPage(); judgePageBoudary();
首先看calcNavigatepageNums()方法:
/** * 計算導航頁 */ private void calcNavigatepageNums() { //當總頁數小於或等於導航頁碼數時 if (pages <= navigatePages) { navigatepageNums = new int[pages]; for (int i = 0; i < pages; i++) { navigatepageNums[i] = i + 1; } } else { //當總頁數大於導航頁碼數時 navigatepageNums = new int[navigatePages]; int startNum = pageNum - navigatePages / 2; int endNum = pageNum + navigatePages / 2; if (startNum < 1) { startNum = 1; //(最前navigatePages頁 for (int i = 0; i < navigatePages; i++) { navigatepageNums[i] = startNum++; } } else if (endNum > pages) { endNum = pages; //最后navigatePages頁 for (int i = navigatePages - 1; i >= 0; i--) { navigatepageNums[i] = endNum--; } } else { //所有中間頁 for (int i = 0; i < navigatePages; i++) { navigatepageNums[i] = startNum++; } } } }
再看 calcPage()方法:
/** * 計算前后頁,第一頁,最后一頁 */ private void calcPage() { if (navigatepageNums != null && navigatepageNums.length > 0) { firstPage = navigatepageNums[0]; lastPage = navigatepageNums[navigatepageNums.length - 1]; if (pageNum > 1) { prePage = pageNum - 1; } if (pageNum < pages) { nextPage = pageNum + 1; } } }
judgePageBoudary()方法:
/** * 判定頁面邊界 */ private void judgePageBoudary() { isFirstPage = pageNum == 1; isLastPage = pageNum == pages; hasPreviousPage = pageNum > 1; hasNextPage = pageNum < pages; }
到此,pageHelper的源碼解讀就結束了,實際上就是通過表示層傳來的頁數和每頁顯示的行數,以此為參數計算出分頁對象page中的相關參數,並將list結果集保存至page中,而page繼承自ArrayList,然后將page對象進行打包存放如pageInfo對象中,最后返回pageInfo對象。