前言
上一篇文章中我們說了下怎麼去使用《通用Mapper》來實作對單表的增删改查功能,本篇我就帶你學習下,如何使用PageHelper插件來實作對單表的分頁功能,至此你就不需要再自己去寫一大堆的mapper.xml代碼去實作單表基礎的功能了(其實我們絕大部分的業務都是基于單表操作的)。
實作目标
先來看一下我們約定的分頁參數和響應結果的樣子。
分頁請求參數:
localhost:8080/zhuma-demo/users?pageNum=1&pageSize=5&orderBy=create_time desc
響應結果:
{
"code": 1,
"msg": "成功",
"data": {
"pageNum": 1,
"pageSize": 5,
"total": 12,
"pages": 3,
"list": [
{
"id": "0c13ec7a-0309-11e8-9139-94de80fdfd41",
"nickname": "小竹馬11",
"gender": "MALE",
"type": "NORMAL",
"status": "ENABLED",
"createTime": 1517019792000,
"updateTime": 1517019792000
},
{
"id": "09c70b22-0309-11e8-9139-94de80fdfd41",
"nickname": "小竹馬10",
"gender": "MALE",
"type": "NORMAL",
"status": "ENABLED",
"createTime": 1517019788000,
"updateTime": 1517019788000
},
//省略
]
}
}
備注
- 參數pageNum代表目前頁号,pageSize代表一頁的數量。
- 參數orderBy用來控制排序(由兩部分組成:第一個值要排序的字段,第二個是排序方式),其實這個跟mysql的order by語句文法是一樣的。
- 傳回結果中total代表記錄總數,pages代表總頁數。
- 傳回結果中list是資料清單,當沒有查詢到資料時,傳回[],空數組。
我們先示範了下做該分頁功能後最終要實作的目标或者說效果,下面就讓我們一起學習下,該怎麼完成上述的分頁功能吧。
說說分頁
通常設計分頁接口參數,常用的有兩種方式用兩個參數兩控制分頁:
- pageNum和pageSize
- offect和limit
他們的換算關系式:offect = (pageNum - 1) * pageSize; limit=pageSize,是以兩種參數方式都是OK也都是很好用的,那我們今天使用的就是pageNum和pageSize這種參數形式,分别代表目前頁号和一頁的數量,這種參數也更友善了解。然後我們在具體講解怎麼做通用分頁。
實作思路
1. 首先引入jar包(使用pageHelper插件進行分頁):
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version><!--使用前請擷取最新版本-->
</dependency>
如果是spring boot項目,可以直接引入:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
Page-helper插件github位址:https://github.com/pagehelper/Mybatis-PageHelper/
2. 幾個核心類
- PageQO:自定義類,用于統一封裝查詢參數,QO譯為query object
- PageVO:自定義類,用于統一封裝分頁響應資料,VO譯為view object
- PageHelper:分頁插件提供,該helper類用于做核心分頁功能
- Page:分頁插件提供,helper傳回值,也同時存在資料庫查詢出來的資料清單資訊
3. 以分頁檢視使用者功能為例
我們自己定義的PageQO類(統一查詢參數):
package com.zhuma.demo.comm.model.qo;
import org.hibernate.validator.constraints.Range;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @desc 分頁查詢對象
* @author zhuamer
* @since 7/6/2017 2:48 PM
*/
@ApiModel("分頁查詢對象")
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageQO<T> {
/**
* 按建立時間倒序排序
*/
public static final String ORDER_BY_CREATE_TIME_DESC = "create_time desc";
@ApiModelProperty(value = "目前頁号")
@Range(min = 1, max = Integer.MAX_VALUE)
private int pageNum = 1;
@ApiModelProperty(value = "一頁數量")
@Range(min = 1, max = Integer.MAX_VALUE)
private int pageSize = 10;
@ApiModelProperty(value = "排序", notes = "例:create_time desc,update_time desc")
private String orderBy;
private T condition;
public PageQO(int pageNum, int pageSize) {
super();
this.pageNum = pageNum;
this.pageSize = pageSize;
}
public int getOffset() {
return (this.pageNum - 1) * this.pageSize;
}
}
我們自己定義的PageVO類(統一封裝分頁響應資料):
package com.zhuma.demo.comm.model.vo;
import java.util.List;
import com.zhuma.demo.comm.model.Model;
import com.zhuma.demo.comm.model.qo.PageQO;
import com.zhuma.demo.util.BeanUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.util.CollectionUtils;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.Lists;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @desc 分頁VO對象
* @author zhuamer
* @since 7/6/2017 2:48 PM
*/
@ApiModel("分頁對象")
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageVO<T> implements Model {
private static final long serialVersionUID = -4426958360243585882L;
@ApiModelProperty(value = "目前頁号")
private int pageNum;
@ApiModelProperty(value = "每頁的數量")
private int pageSize;
@ApiModelProperty(value = "總記錄數")
private long total;
@ApiModelProperty(value = "總頁數")
private int pages;
@ApiModelProperty(value = "結果集")
private List<T> list;
public PageVO(PageQO pageQO) {
this.setPageNum(pageQO.getPageNum());
this.setPageSize(pageQO.getPageSize());
}
public PageVO(List<T> poList) {
BeanUtils.copyProperties(new PageInfo<>(poList), this);
}
public static <T> PageVO<T> build(List<T> poList) {
return new PageVO<>(poList);
}
/**
* @desc 建構一個分頁VO對象
*
* @param page 資料庫查出來的分頁資料清單
*/
public static <T> PageVO<T> build(Page<T> page) {
PageVO<T> pageVO = new PageVO<>();
BeanUtils.copyProperties(page.toPageInfo(), pageVO);
return pageVO;
}
/**
* @desc 建構一個分頁VO對象
* 試用場景為:從資料庫取出的PO清單不做任何處理,轉化為VO清單傳回
*
* @param page 資料庫查出來的分頁資料清單
* @param voClazz 要轉為的VO類
*/
public static <T, E> PageVO<T> build(Page<E> page, Class<T> voClazz) {
PageVO<T> pageVO = new PageVO<>();
BeanUtils.copyProperties(page, pageVO, "list");
try {
List<T> VOs = Lists.newArrayList();
List<E> POs = page.getResult();
if (!CollectionUtils.isEmpty(POs)) {
for (E e : POs) {
T t = voClazz.newInstance();
BeanUtils.copyProperties(e, t);
VOs.add(t);
}
}
pageVO.setList(VOs);
} catch (IllegalAccessException | InstantiationException e) {
throw new RuntimeException(e);
}
return pageVO;
}
/**
* @desc 建構一個分頁VO對象
* 試用場景為:将處理好的VO清單封裝傳回
*
* @param poPage 資料庫查出來的分頁資料
* @param voList vo資料清單
*/
public static <T, E> PageVO<T> build(Page<E> poPage, List<T> voList) {
PageVO<T> page = new PageVO<>();
BeanUtil.copyProperties(poPage, page, "list");
page.setList(voList == null ? Lists.newArrayList() : voList);
return page;
}
public static int getPages(long total, int pageSize) {
if (total == 0 || pageSize == 0) {
return 0;
}
return (int) (total % pageSize == 0 ? (total / pageSize) : (total / pageSize + 1));
}
public int getPages(){
return getPages(this.total, this.pageSize);
}
}
使用者Controller類(其中getPage方法就是我們本章的核心啦):
package com.zhuma.demo.web.user;
import java.util.Date;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.zhuma.demo.annotation.ResponseResult;
import com.zhuma.demo.comm.model.qo.PageQO;
import com.zhuma.demo.comm.model.vo.PageVO;
import com.zhuma.demo.comm.result.DefaultErrorResult;
import com.zhuma.demo.comm.result.PlatformResult;
import com.zhuma.demo.comm.result.Result;
import com.zhuma.demo.exception.BusinessException;
import com.zhuma.demo.exception.DataNotFoundException;
import com.zhuma.demo.exception.UserNotLoginException;
import com.zhuma.demo.mapper.UserMapper;
import com.zhuma.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import com.zhuma.demo.model.po.User;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @desc 使用者管理控制器
*
* @author zhumaer
* @since 6/20/2017 16:37 PM
*/
@ResponseResult
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping
public PageVO<User> getPage(PageQO pageQO) {
Page<User> page = PageHelper.startPage(pageQO.getPageNum(), pageQO.getPageSize(), pageQO.getOrderBy());
userMapper.selectAll();
return PageVO.build(page);
}
}
分析講解
上面就是我們利用pageHelper實作的一個使用者的分頁功能啦(selectAll方法是使用通用mapper的),調用方法的結果在上面實作目标中已經展示出來啦,是不是還算蠻簡單呢,不需要寫任何的mapper類和mapper.xml就可以實作了,不過你可能有點看不太懂了,下面我已我自己的使用前的疑問依依解答一下:
① 該分頁方式是記憶體分頁還是實體分頁?
答:pagehelper分頁是屬于實體分頁的,不是記憶體分頁,是以不用擔心資料量過大導緻的性能問題。
② userMapper.selectAll()這個方法什麼都沒有傳回,為什麼直接使用page就可以得到結果呢?
答:其實你在調用PageHelper.startPage靜态方法的時候,PageHelper會把每一次調用的page參數放入ThreadLocal變量中,因為Threadlocal本身就是線程安全的,是以在多線程環境下,各個Threadlocal之間互相隔離,不同thread可以使用不同的資料源或執行不同的SQL語句,PageHelper利用這一點通過攔截器擷取到同一線程中的預編譯好的SQL語句之後将SQL語句包裝成具有分頁功能的SQL語句,并将其再次指派給下一步操作,是以實際執行的SQL語句就是有了分頁功能的SQL語句,執行成功後,資料結果會被寫回Threadlocal的page變量中。
③ 這種PageHelper.startPage 靜态方法的方式,使用的規則或者說我們要怎麼具體的使用它呢?
答:使用起來很簡單,你隻需在你需要進行分頁的 MyBatis 查詢方法前調用 PageHelper.startPage 靜态方法即可,緊跟在這個方法後的第一個MyBatis 查詢方法會被進行分頁。
④ 使用這種分頁方式需要注意什麼呢?
答: 使用了PageHelper.startPage方法後,一定要跟一個查詢資料庫的操作,否則會導緻不安全的分頁,所謂的不安全分頁,比如說在你的方法中使用了該startPage靜态方法,但是後面卻沒有查詢資料庫操作,就會導緻 PageHelper 生産了一個分頁參數,但是沒有被消費,這個參數就會一直保留在這個線程上。當這個線程再次被使用時,就可能導緻不該分頁的方法去消費這個分頁參數,這就産生了莫名其妙的分頁。
課外擴充
學過了通用mapper和page-helper的使用後,我們其實可以直接寫出一個相對比較通用的分頁方法,下面給出代碼,留給同學們課後回味和研究下它的好用之處吧O(∩_∩)O~
public PageVO<E> selectPage(PageQO<?> pageQO) {
Assert.notNull(pageQO, "pageQO is not null");
Page<E> page = PageHelper.startPage(pageQO.getPageNum(), pageQO.getPageSize(), pageQO.getOrderBy());
try {
Object condition = pageQO.getCondition();
if (condition == null) {
crudMapper.selectAll();
} else if (condition instanceof Condition) {
crudMapper.selectByCondition(condition);
} else if (condition instanceof Example) {
crudMapper.selectByExample(condition);
} else if (poType.isInstance(condition)){
crudMapper.select((E)condition);
} else {
try {
E e = poType.newInstance();
BeanUtil.copyProperties(condition, e);
crudMapper.select(e);
} catch (InstantiationException | IllegalAccessException e) {
log.error("selectPage occurs error, caused by: ", e);
throw new RuntimeException("poType.newInstance occurs InstantiationException or IllegalAccessException", e);
}
}
} finally {
page.close();
}
return PageVO.build(page);
}
最後
關于通用分頁就講解到這裡,下一篇文章會介紹在項目中如果實作通用Service,不知道你對該篇文章還有什麼疑惑呢,可以關注我的微信号或留言,很願意和你一起讨論學習O(∩_∩)O~
最後附上github位址:https://github.com/zhumaer/zhuma/tree/master/zhuma-demo
QQ群号:629446754(歡迎加群)
歡迎關注我們的公衆号或加群,等你哦!