最近項目又用到了Mybaits。在Mybatis中分頁是個比較頭疼的事,因為需要我們每次都寫重復的sql。好在我們有PageHelper這樣的分頁工具,它可以攔截你的sql,從而進行分頁操作。
一、使用PageHelper分頁和遇到的問題
首先我們引入maven依賴。
<pagehelper-version>1.2.5</pagehelper-version>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper-version}</version>
</dependency>
然后假定你有個需要分頁的查詢方法selectList()
,已經定義到mapper中了。這個方法是不分頁的,因此你不必寫任何分頁的語句。
在service
或者persistance
層,你需要定義一個分頁查詢的方法selectPage()
。
public PageBean<ReportTemplate> selectPage(PageBean<User> page) {
//PageHelper設置當前頁和頁大小
PageHelper.startPage(page.getPageNo(), page.getPageSize());
PageHelper.orderBy(page.getSortedField());
List<User> users= userMapper.selectPage(page.getKeyWords());
PageInfo<User> pageInfo = new PageInfo<>(users);
page.setCount(pageInfo.getTotal());
page.setList(pageInfo.getList());
return page;
}
這樣我們就完成了PageHelper的分頁。我們總結下使用PageHelper分頁查詢的步驟。
- 編寫一個查詢sql
- 編寫一個分頁查詢方法,設置PageHelper的當前頁和頁大小
- 執行查詢語句
- 查詢完成后把PageInfo的數據填充到自定義的PageBean中
以上四個步驟我們可以看出,除了第三步是真正需要我們手動寫sql的,其他的步驟都是重復的過程。一個合格的程序員一定不要寫重復的代碼,那么我們有沒有什么辦法能去掉重復的代碼呢?
首先你可能想到把PageHelper的設置和自定義PageBean的數據填充分別封裝成一個函數,然后每次調用兩個方法就行了。雖然這樣確實能減少一定的代碼,但是僅僅是減少了部分代碼,還是沒達到我想的效果。我們想的是最后能像lombok那樣簡單易用,只需要一個注解就能省掉許多代碼。那么我們應該如果做到呢?
二、使用Spring boot AOP和自定義注解
AOP的概念相信閱讀本文的人應該都有所了解,但是真正用起來的不算多。我這里不想談AOP的概念,因為你可以很容易地從百度/谷歌上找到相關的文章。我會在文章的最后放上我查閱過的文章鏈接,以供你參考。
AOP的作用就是無侵入地實現部分功能,例如日志記錄,操作記錄等。
首先引入Spring boot AOP依賴
<!-- 引入Spring boot AOP依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
然后我們先不管AOP的事,我們先定義一個我們需要的注解。一般來說只使用AOP就可以無侵入地實現分頁,但是麻煩的一點在於AOP中切點匹配規則的擴展性不夠強。尤其是我們開發過程中添加新功能往往會新增一個package,這就可能導致AOP的失效。因此我們在此處使用自定義注解,這樣對於任意我們想分頁的方法,只需要在上面加一個注解就能實現分頁,這樣用起來更靈活一些。
自定義注解很簡單,你無須寫任何邏輯代碼。我們可以如下定義一個注解。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnablePaging {
}
定義好注解之后僅僅意味着你能在方法上使用此注解了。當然,你可以通過類的操作獲取到這個注解。但是這還無法實現分頁操作。我們需要寫AOP的相關代碼來實現分頁操作了。
package org.flow.approval.annotation;
import com.github.pagehelper.PageHelper;
import com.google.common.base.Strings;
import com.sino.common.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.flow.bean.PageBean;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* @Description: Mybatis排序的切入點實現方法
* @Author: gaoyakang
* @Version: 1.0
* @Create Date Time: 2020-05-07 17:52
* @Update Date Time:
* @see EnablePaging
*/
@Aspect
@Slf4j
@Component
public class MybatisPageEvent {
/**
* 啟用分頁實現類
*
* @param point 切入點
* @return 切入點的執行結果
* @throws Throwable 可能出現的異常
*/
@Around("@annotation(EnablePaging)")
public Object doPaging(ProceedingJoinPoint point) throws Throwable {
Object[] args = point.getArgs();
//獲取第一個參數
Object obj = args[0];
//僅僅當第一個參數為PageBean的時候才執行分頁
if (obj instanceof PageBean) {
PageBean pageBean = (PageBean) obj;
Integer pageNum = pageBean.getPageNo();
Integer pageSize = pageBean.getPageSize();
if (Objects.isNull(pageSize)) {
pageSize = 10;
}
if (Objects.isNull(pageNum)) {
pageNum = 1;
}
//使用PageHelper進行分頁
PageHelper.startPage(pageNum, pageSize);
log.info("啟動分頁方法,當前分頁頁號為:{},頁大小:{}", pageNum, pageSize);
}
return point.proceed(args);
}
}
這里的代碼也很簡單。但是你需要按照你的需要來寫,我認為照搬代碼畢竟不可取。因為在我的項目中,我定義了一個PageBean,用來保存分頁的一些信息,如當前頁、頁大小、分頁后的數據、總數等信息。因此在我的項目中所有分頁的方法,第一個參數必須是PageBean。在你的項目中,可能你是直接傳rawType參數到方法中的,這里需要你按照自己的需要進行處理。
因為這里是環繞通知,所以我們也可以對方法調用之后的結果進行處理。
//分頁邏輯省略......
Object result=point.proceed(args);
if(resultinstanceof List) {
List objList = (List) result;
PageInfo pageInfo = new PageInfo<>(objList);
return pageInfo;
}
實現上述操作之后的分頁方法就變得簡單多了
@Override
@EnablePaging
public PageBean<User> selectPage(PageBean<User> page) {
List<User> list = userMapper.selectPageData(page.getKeyWords());
PageInfo<User> pageInfo = new PageInfo<>(list);
//我的項目中因為需要特殊的操作,因此沒有使用后置通知處理
page.setCount((int) pageInfo.getTotal());
page.setList(pageInfo.getList());
return page;
}
三、需要注意的是......
這里我遇到的問題是,在一些情況下AOP會失效。例如我現在有ControllerA,ServiceB,ServiceB中的方法fun1和fun2。這個三個層次,A調用了fun1(),fun1()調用fun2()。這種情況下AOP失效了。但是直接調用fun2()可以完美分頁。暫時我還沒發現為何會出現這種問題,如果你有任何見解或者看法,可以在評論區留言。
參考文章
博客園:Spring AOP + PageHelper分頁
博客園:Spring Boot:實現MyBatis分頁
Java獲取類方法上的注解
比較 Spring AOP 與 AspectJ
檸檬五個半:Spring AOP SpringBoot集成