在做某购物商城的系统时,遇到了需要分页的情况,采用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对象。