天天看點

Spring Boot項目通用功能之《通用分頁》

前言

上一篇文章中我們說了下怎麼去使用《通用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(歡迎加群)

Spring Boot項目通用功能之《通用分頁》

歡迎關注我們的公衆号或加群,等你哦!

Spring Boot項目通用功能之《通用分頁》
Spring Boot項目通用功能之《通用分頁》
Spring Boot項目通用功能之《通用分頁》

繼續閱讀