Mybatis 使用Spring boot AOP +自定義注解+PageHelper實現分頁


最近項目又用到了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分頁查詢的步驟。

  1. 編寫一個查詢sql
  2. 編寫一個分頁查詢方法,設置PageHelper的當前頁和頁大小
  3. 執行查詢語句
  4. 查詢完成后把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集成


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM