Mybatis 分頁組件PageHelper 使用

PageHelper 官網:

  1. https://pagehelper.github.io
  2. 關於PageHelper 的開發和原理官網上也已經講的很明確了,這裏不過多解析官網的意思

快速構建:

  1. 首先需要一個 SSM 項目【也可以單體Mybatis】
  2. 新增Maven 配置:
<dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper</artifactId>
        <version>5.1.10</version>
      </dependency>
  1. mybatis-config.xml 加入以下配置【也可以寫在Spring的配置中,寫法略有不同】:
<configuration>
    <!--Mysql 配置駝峯命名-->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <!--分頁插件-->
    <plugins>
        <!-- com.github.pagehelper爲PageHelper類所在包名 -->
        <plugin interceptor="com.github.pagehelper.PageInterceptor" >
        </plugin>
    </plugins>

</configuration>
  1. Dao 以及 Xml 寫法。
// Dao
List<ClassInfo> getListByPage();
<!-- XML -->
<select id="getListByPage" parameterType="com.github.pagehelper.PageInfo" resultType="com.morning.all.entity.test.ClassInfo">
        select * from  tb_class
    </select>
  1. 重點Spring 的實現類:
/**
     * 分頁查詢數據
     * @return
     */
    public PageInfo getListByPage(PageInfo pageInfo){
        //設置分頁信息(第幾頁,每頁數量),並將Page 對象放入到 ThreadLocal 中,
        PageHelper.startPage(pageInfo.getPageNum(), pageInfo.getPageSize());
        //查詢結果,我們的攔截器會在這裏起作用
        List<ClassInfo> result = classDao.getListByPage();
        // 生成返回結果
        PageInfo<ClassInfo> pageInfoResult = new PageInfo<ClassInfo>(result);
        return pageInfoResult;
    }

這裏需要注意的是,getListByPage 的參數PageInfo 僅僅由於接受 前端分頁參數,不可用於返回。

  1. 通過Controller執行我們的代碼,看一下Mybatis sql打印結果:
    在這裏插入圖片描述
    哦吼,爲什麼會打印出這兩條SQL ,一條Count,一條Select,我原來的Sql 爲什麼沒有執行那,不着急我們看下一部分

源碼淺析:

  1. 要想真正理解這個功能,需要對Mybatis源碼 和 Mybatis攔截器有一定的瞭解,那麼PageInterceptor中究竟做了什麼那,直接上源碼,因爲它是中國人開發的,代碼註釋也一目瞭然,完全Copy
package com.github.pagehelper;
public class PageInterceptor implements Interceptor {
    private volatile Dialect dialect;
    private String countSuffix = "_COUNT";
    protected Cache<String, MappedStatement> msCountMap = null;
    private String default_dialect_class = "com.github.pagehelper.PageHelper";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            Object[] args = invocation.getArgs();
            MappedStatement ms = (MappedStatement) args[0];
            Object parameter = args[1];
            RowBounds rowBounds = (RowBounds) args[2];
            ResultHandler resultHandler = (ResultHandler) args[3];
            Executor executor = (Executor) invocation.getTarget();
            CacheKey cacheKey;
            BoundSql boundSql;
            //由於邏輯關係,只會進入一次
            if (args.length == 4) {
                //4 個參數時
                boundSql = ms.getBoundSql(parameter);
                cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
            } else {
                //6 個參數時
                cacheKey = (CacheKey) args[4];
                boundSql = (BoundSql) args[5];
            }
            checkDialectExists();

            List resultList;
            //調用方法判斷是否需要進行分頁,如果不需要,直接返回結果
            if (!dialect.skip(ms, parameter, rowBounds)) {
                //判斷是否需要進行 count 查詢
                if (dialect.beforeCount(ms, parameter, rowBounds)) {
                    //查詢總數
                    Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                    //處理查詢總數,返回 true 時繼續分頁查詢,false 時直接返回
                    if (!dialect.afterCount(count, parameter, rowBounds)) {
                        //當查詢總數爲 0 時,直接返回空的結果
                        return dialect.afterPage(new ArrayList(), parameter, rowBounds);
                    }
                }
                resultList = ExecutorUtil.pageQuery(dialect, executor,
                        ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
            } else {
                //rowBounds用參數值,不使用分頁插件處理時,仍然支持默認的內存分頁
                resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
            }
            return dialect.afterPage(resultList, parameter, rowBounds);
        } finally {
            if(dialect != null){
                dialect.afterAll();
            }
        }
    }

    /**
     * Spring bean 方式配置時,如果沒有配置屬性就不會執行下面的 setProperties 方法,就不會初始化
     * <p>
     * 因此這裏會出現 null 的情況 fixed #26
     */
    private void checkDialectExists() {
        if (dialect == null) {
            synchronized (default_dialect_class) {
                if (dialect == null) {
                    setProperties(new Properties());
                }
            }
        }
    }

    private Long count(Executor executor, MappedStatement ms, Object parameter,
                       RowBounds rowBounds, ResultHandler resultHandler,
                       BoundSql boundSql) throws SQLException {
        String countMsId = ms.getId() + countSuffix;
        Long count;
        //先判斷是否存在手寫的 count 查詢
        MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);
        if (countMs != null) {
            count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
        } else {
            countMs = msCountMap.get(countMsId);
            //自動創建
            if (countMs == null) {
                //根據當前的 ms 創建一個返回值爲 Long 類型的 ms
                countMs = MSUtils.newCountMappedStatement(ms, countMsId);
                msCountMap.put(countMsId, countMs);
            }
            count = ExecutorUtil.executeAutoCount(dialect, executor, countMs, parameter, boundSql, rowBounds, resultHandler);
        }
        return count;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        //緩存 count ms
        msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);
        String dialectClass = properties.getProperty("dialect");
        if (StringUtil.isEmpty(dialectClass)) {
            dialectClass = default_dialect_class;
        }
        try {
            Class<?> aClass = Class.forName(dialectClass);
            dialect = (Dialect) aClass.newInstance();
        } catch (Exception e) {
            throw new PageException(e);
        }
        dialect.setProperties(properties);

        String countSuffix = properties.getProperty("countSuffix");
        if (StringUtil.isNotEmpty(countSuffix)) {
            this.countSuffix = countSuffix;
        }
    }

}

  1. Mybatis 執行 Dao層方法時執行時,會被攔截器攔截執行Interceptor.intercept()方法,我們可以再源碼中看到 第37行時查詢了總條數,在第44行執行了 分頁查詢,也就是說PageInterceptor會攔截請求,找到我們本來要執行的Sql並將其拆解,生成 count 和 Select Limit Sql 執行,並最後將結果返回。
  2. 那麼爲什麼我們原來的XML 配置的sql 並沒有執行那,是因爲Mybatis執行XML中的sql 也是一個攔截器,Mybatis執行攔截器是責任鏈的形式,而Mybatis自己的攔截器優先級最低,先執行了 PageInterceptor,並且PageInterceptor 中沒有調用 Mybatis本身的攔截器,所以就沒有執行。需要在PageInterceptor.intercept() 中加入代碼: Object result = invocation.proceed(); 程序纔會回去執行下一級攔截器,顯然這裏根本不需要執行下一個攔截器

以上僅爲博主的粗淺認識,如有疑問或建議,可以留言多多探討,博主會第一時間更正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章