1 简介
在后端与前端进行交互的过程中,需要对后端得到的数据进行分页推送给前端,比如说在某个博客网站上某用户编写了100篇文档,但在页面展示时,可能在每个页面仅仅展示10条数据,如下图所示
因此,而且此类需求是一个常见需求,所以可以总结一下这个用法。
一般需要实现该情景,需要返回的类似数据如下:
{
"result": "success",
"msg": "查询历史信息成功",
"data": {
"totalPage": 66,
"pageSize": 10,
"currentPage": 1,
"rows": [
{
"month": "08",
"createTime": "2018-12-24 16:15:36",
"year": "2018",
"description": "完成2018年08月的缴纳, 金额为800.0元",
"operation": "缴纳",
"operator": "周周"
},
{
"month": "08",
"createTime": "2018-12-24 15:22:01",
"year": "2018",
"description": "完成2018年08月购买的发起",
"operation": "党费发起",
"operator": "周周"
},
{
"month": "08",
"createTime": "2018-12-24 15:13:36",
"year": "2018",
"description": "完成文件的上传,可以发起2018年08月的购买流程",
"operation": "上传工资",
"operator": "周周"
},
{
"month": "03",
"createTime": "2018-12-24 10:52:40",
"year": "2021",
"description": "完成2021年03月购买的部门领导",
"operation": "部门领导",
"operator": "猪八戒"
},
{
"month": "03",
"createTime": "2018-12-24 10:52:07",
"year": "2021",
"description": "完成综合计划室党支部-政工组2021年03月购买的确认",
"operation": "党支部确认",
"operator": "松松"
},
{
"month": "03",
"createTime": "2018-12-24 10:51:47",
"year": "2021",
"description": "完成2021年03月的购买, 金额为500.0元",
"operation": "购买",
"operator": "松松"
},
{
"month": "03",
"createTime": "2018-12-24 10:51:27",
"year": "2021",
"description": "完成开发组2021年03月购买的确认",
"operation": "党支部确认",
"operator": "范范"
},
{
"month": "03",
"createTime": "2018-12-24 10:51:22",
"year": "2021",
"description": "为吵吵代办2021年03月购买,金额为222.0元",
"operation": "党费代办",
"operator": "范范"
},
{
"month": "03",
"createTime": "2018-12-24 10:51:12",
"year": "2021",
"description": "为猪八戒代办2021年03月购买,金额为555.0元",
"operation": "党费代办",
"operator": "范范"
},
{
"month": "03",
"createTime": "2018-12-24 10:50:43",
"year": "2021",
"description": "完成2021年03月的购买, 金额为255.0元",
"operation": "购买",
"operator": "范范"
}
],
"totalCount": 654
}
}
一般在分页结构中应该包含的最少内容如下:
{
“result”: “success”,
“msg”: “获取数据成功”,
“data”: {
“totalCount”: 300,
"totalPage": 66,
"pageSize": 10,
"currentPage": 1,
rows: [{
…
},
{
…
},
{
…
},
{
…
},
…]
}
}
因此,在定义该类时,应该包含上述5个成员totalCount, totalPage, pageSize, currentPage, 以及保存当前页对应的所有数据的rows成员.
2 定义
package com.sqh.util;
import java.io.Serializable;
import java.util.List;
public class Page<T> implements Serializable {
private static final long serialVersionUID = 5760097915453738435L;
public static final int DEFAULT_PAGE_SIZE = 10;
/**
* 每页显示个数
*/
private int pageSize;
/**
* 当前页数
*/
private int currentPage;
/**
* 总页数
*/
private int totalPage;
/**
* 总记录数
*/
private int totalCount;
/**
* 结果列表
*/
private List<T> rows;
public Page(){
this.currentPage = 1;
this.pageSize = DEFAULT_PAGE_SIZE;
}
public Page(int currentPage,int pageSize){
this.currentPage=currentPage<=0?1:currentPage;
this.pageSize=pageSize<=0?1:pageSize;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public int getCurrentPage() {
return currentPage;
}
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}
public int getTotalPage() {
return totalPage;
}
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
public int getTotalCount() {
return totalCount;
}
public void setTotalCount(int totalCount) {
//设置了totalCount就可以计算出总totalPage
this.totalCount = totalCount;
int countRecords = this.getTotalCount();
int totalPages = countRecords % pageSize == 0 ? countRecords / pageSize : (countRecords / pageSize + 1);
setTotalPage(totalPages);
}
/**
* 设置结果 及总页数
* @param rows 分页之后查询到的结果
*/
public void build(List<T> rows) {
this.setRows(rows);
int count = this.getTotalCount();
/*
int divisor = count / this.getPageSize();
int remainder = count % this.getPageSize();
//设置总页数, Trash code, confusing.
this.setTotalPage(remainder == 0 ? (divisor == 0 ? 1 : divisor) : divisor + 1);
*/
//已在setTotalCount中进行
/*
int countRecords = this.getTotalCount();
int totalPages = countRecords % pageSize == 0 ? countRecords / pageSize : (countRecords / pageSize + 1);
setTotalPage(totalPages);
*/
}
public List<T> getRows() {
return rows;
}
public void setRows(List<T> rows) {
this.rows = rows;
}
}
在该类使用时,应该首先使用步骤如下:
- currentPage和 pageSize进行分页类Page对象的实例化,
- 然后使用setTotalCount()函数传入总记录数,
- 这样在把当前页结果给取出来,传入Page对象,即可封装该分页结构
3 使用
3.1 Mongo数据库分页查询
在与mongo数据库进行交互时,由于没有直接提供分页的函数,因此我们可对这种应用场景进行封装
public Page<T> findPage(Page<T> page, Query query,String collectionName){
//如果没有条件 则所有全部
query=query==null?new Query(Criteria.where("_id").exists(true)):query;
long count = this.count(query, collectionName);
// 总数
page.setTotalCount((int) count);
int currentPage = page.getCurrentPage();
int pageSize = page.getPageSize();
query.skip((currentPage - 1) * pageSize).limit(pageSize);
List<T> rows = this.find(query,collectionName);
page.build(rows);
return page;
}
在上述的普通函数中,我们调用了Query类型,
public class Query extends Object
MongoDB Query类对象表示规则Criteria,投射Projection,排序sorting,和Query Hints。使用了mongoTemplate对象进行查询和计数。可查询相关API,不再赘述。
@RequestMapping(value = "/partydues/viewSalarayInfo", method = RequestMethod.POST)
@ResponseBody
public String viewSalarayInfo(@RequestBody JSONObject form) {
System.out.println(("viewSalarayInfo starts"));
JSONObject result = new JSONObject();
if ((!form.containsKey("year")) || (!form.containsKey("month"))) {
return result.element("result", "fail").element("msg", "查询工资时请指定年月参数").toString();
}
String year = form.getString("year");
String month = form.getString("month");
//获取分页参数并验证数据有效性
if ((year.isEmpty()) || (month.isEmpty())) {
return result.element("result", "fail").element("msg", "查询工资时年月参数不能为空").toString();
}
if ((!form.containsKey("pageSize")) || (!form.containsKey("page"))) {
return result.element("result", "fail").element("msg", "查询时请指定分页信息").toString();
}
int page = form.getInt("page");
int pageSize = form.getInt("pageSize");
if (pageSize < 0) {
pageSize = 10;
}
if (page<0) {
page=0;
}
//创建分页对象
Page<FreeModel> pageResult = new Page<FreeModel>();
pageResult.setPageSize(pageSize);
pageResult.setCurrentPage(page);
Query query = new Query();
query.addCriteria(new Criteria("map.year").is(year));
query.addCriteria(new Criteria("map.month").is(month));
query.with(new Sort(new Sort.Order(Sort.Direction.ASC, "map.orderBy")));
//使用上述封装的函数,传入分页对象,和表名,这样在函数执行时自动填充totalCount,和rows
freeDao.findPage(pageResult, query, SALARY_TABLE);
List<FreeModel> rows = pageResult.getRows();
JSONArray array = new JSONArray();
//重组rows中的内容
for(int i=0; i<rows.size(); i++) {
//检索第一个元素,按照指定的形式返回前端数据
PageData pageData = rows.get(i).getMap();
JSONObject salaryRecord = JSONObject.fromObject(pageData);
array.add(salaryRecord);
}
pageResult.setRows(array);
return CommonReturn.httpReturn(CommonReturn.SUCCESS, "查询工资记录成功", pageResult);
}
可见,在上述的Controller层调用时依然遵循了相同的Page对象使用步骤。
3.2 普通List对象组装
在Java web开发的过程中,也存在一种情形,需要我们自己组织list数据,并返回给前端符合分页结构的数据,这也是一种常见的情形,对于这类情形,如何使用Page类进行分页对象的构建呢?查看下述例子:
/**
* @description: 返回指定年月的工资信息,展示列表,支持分页展示
* @url:
* @author: Song Quanheng
* @date: 2018/11/13-14:42
* @return:
*/
@RequestMapping(value = "/partydues/viewSalarayInfoByDept/{year}/{month}")
@ResponseBody
public String viewSalaryInfoByDept(@PathVariable("year") String year,
@PathVariable("month") String month, @RequestBody JSONObject form) {
JSONObject res = new JSONObject();
if (!form.containsKey("page") || !form.containsKey("pageSize")) {
return CommonReturn.httpReturnFailure("请指定分页参数page和pageSize");
}
int page = form.getInt("page");
int pageSize = form.getInt("pageSize");
if (pageSize < 0) {
pageSize = 10;
}
if (page<=0) {
page=1;
}
Query query = new Query();
query.addCriteria(new Criteria("map.year").is(year));
query.addCriteria(new Criteria("map.month").is(month));
JSONObject ret = partyDuesBusiness.findSalaryInfoByDept(year, month);
if (ret.size() == 0) {
return CommonReturn.httpReturnFailure("查询不到"+year+"年"+month+"月的工资信息");
}
JSONArray deptOrder = partyDuesBusiness.viewDeptInfoInOrder();
JSONArray result = new JSONArray();
if (0 == deptOrder.size()) {
return CommonReturn.httpReturn(FAILURE, "请插入有序的部门名称到数据库中");
}
for (int i=0; i<deptOrder.size(); i++) {
String deptName = deptOrder.getString(i);
int count = 0;
if (ret.containsKey(deptName)) {
//根据部门名获得该部门相关的人数
count = ret.getInt(deptName);
} else {
continue;
}
//如果0==count,表示该部门不用展示在页面上,因为不存在人员
if (0==count) {
continue;
}
JSONObject itemOne = new JSONObject();
itemOne.put("deptName", deptName);
itemOne.put("totalNumOfPersons", count);
itemOne.put("year", year);
itemOne.put("month", month);
result.add(itemOne);
}
int countRecords = result.size();
int totalPages = countRecords % pageSize == 0 ? countRecords / pageSize : (countRecords / pageSize + 1);
//获得指定范围的结果
List<JSONObject> pageResult = partyDuesBusiness.getListByPage(result, page, pageSize);
//组织为分页对象
Page pageRet = new Page(page, pageSize);
pageRet.setRows(pageResult);
pageRet.setTotalPage(totalPages);
pageRet.setTotalCount(countRecords);
return CommonReturn.httpReturn(CommonReturn.SUCCESS, "按照部门分组查询信息成功", pageRet);
}
上述的代码遵循相同的步骤逻辑,查询分页范围内的结果,然后利用当前页和页面记录数新建分页对象,设置totalCount成员,最后设置分页范围的记录内容。返回给前端即可。
3.3 getListByPage
在上述普通的list对象生成分页数据的过程中,调用了一个函数getListByPage()函数,该函数封装内容如下:
public List getListByPage(List list,int page,int pageSize) {
if(list.size() > 0 ){
int firstIndex = (page - 1) * pageSize;
int lastIndex = page * pageSize;
int actualLastIndex = 0;
if(list.size() > lastIndex || list.size() == lastIndex){
actualLastIndex = lastIndex;
}else{
actualLastIndex = list.size();
}
return list.subList(firstIndex,actualLastIndex);
}
return list;
}
函数中主要使用List接口的subList函数。
List表示有序的collection。此接口的用户可以对列表中的每个元素的插入位置进行精确的控制。用户可以根据元素的整数索引访问元素,并搜索列表中元素的位置。
List<E> subList(int fromIndex, int toIndex)
返回列表中指定的fromIndex(包括)和toIndex(不包括)之间的部分视图。
返回:
列表中指定范围的视图
抛出:IndexOutOfBoundsException – 非法的端点值(fromIndex<0 || toIndex > size || fromIndex > toIndex)
注意:由于getListByPage中list为List类型,因此只要类型实现了List接口,均可以传入,诸如ArrayList或者JSONArray都可以传入该函数进行分页提取数据。
4 总结
在编程过程中,对于不断重复的模式可以进行封装,这样既能锤炼代码的凝练度,同时可以增强代码的正确性。Java分页相关的内容介绍到这里,不断的反思和总结是一个人持续进步的基石,是每个程序员自我要求,自我实现的一部分。