基於Mybatis分頁插件PageHelper


基於Mybatis分頁插件PageHelper

1.分頁插件使用

1POM依賴

PageHelper的依賴如下。需要新的版本可以去maven上自行選擇

 

<!-- PageHelper 插件分頁 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>4.0.1</version>
</dependency>

 

2MybatisPageHelper的配置

打開Mybatis配置文件,一般在Resource路徑下mybatis-config.xml

 

<configuration>
   <settings>
        <!-- 設置啟用數據庫字段下划線映射到java對象的駝峰式命名屬性,默認為false -->
          <setting name="mapUnderscoreToCamelCase" value="true" />
     </settings>
     <objectWrapperFactory type="com.maomao.xwz.mybatis.handle.MapWrapperFactory"/>
    <plugins>
        <!-- com.github.pagehelper為PageHelper類所在包名 -->
        <plugin interceptor="com.github.pagehelper.PageHelper">
            <!-- 4.0.0以后版本可以不設置該參數 -->
            <property name="dialect" value="mysql"/>
            <!-- 該參數默認為false -->
            <!-- 設置為true時,會將RowBounds第一個參數offset當成pageNum頁碼使用 -->
            <!-- 和startPage中的pageNum效果一樣-->
            <property name="offsetAsPageNum" value="true"/>
            <!-- 該參數默認為false -->
            <!-- 設置為true時,使用RowBounds分頁會進行count查詢 -->
            <property name="rowBoundsWithCount" value="true"/>
            <!-- 設置為true時,如果pageSize=0或者RowBounds.limit = 0就會查詢出全部的結果 -->
            <!-- (相當於沒有執行分頁查詢,但是返回結果仍然是Page類型)-->
            <property name="pageSizeZero" value="true"/>
            <!-- 3.3.0版本可用 - 分頁參數合理化,默認false禁用 -->
            <!-- 啟用合理化時,如果pageNum<1會查詢第一頁,如果pageNum>pages會查詢最后一頁 -->
            <!-- 禁用合理化時,如果pageNum<1或pageNum>pages會返回空數據 -->
            <property name="reasonable" value="false"/>
            <!-- 3.5.0版本可用 - 為了支持startPage(Object params)方法 -->
            <!-- 增加了一個`params`參數來配置參數映射,用於從Map或ServletRequest中取值 -->
            <!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,orderBy,不配置映射的用默認值 -->
            <!-- 不理解該含義的前提下,不要隨便復制該配置 -->
            <property name="params" value="pageNum=start;pageSize=limit;"/>
            <!-- 支持通過Mapper接口參數來傳遞分頁參數 -->
            <property name="supportMethodsArguments" value="true"/>
            <!-- always總是返回PageInfo類型,check檢查返回類型是否為PageInfo,none返回Page -->
            <property name="returnPageInfo" value="check"/>
        </plugin>
    </plugins>
</configuration>

 

3PageHelper分頁使用

1)只統計查詢總數量count

public PageInfo selectByExample() {
    PageHelper.startPage(1, -1);
    List list = this.baseMapper.selectByExample(new BsFeedbackExample());
    PageInfo pageInfo = new PageInfo(list);
    log.info(JSONObject.toJSONString(pageInfo));
    return pageInfo;
}

執行sql日志:

 

 

2)正常分頁

public PageInfo selectByExample1() {
    PageHelper.startPage(1, 1);
    List list = this.baseMapper.selectByExample(new BsFeedbackExample());
    PageInfo pageInfo = new PageInfo(list);
    log.info(JSONObject.toJSONString(pageInfo));
    return pageInfo;
}

執行sql日志:

 

 

3)分頁統計總數

public PageInfo selectByExample2() {
    PageHelper.startPage(1, 1, true);
    List list = this.baseMapper.selectByExample(new BsFeedbackExample());
    PageInfo pageInfo = new PageInfo(list);
    log.info(JSONObject.toJSONString(pageInfo));
    return pageInfo;
}

執行sql日志:

 

 

4) 分頁不統計總數

public PageInfo selectByExample3() {
    PageHelper.startPage(1, 1, false);
    List list = this.baseMapper.selectByExample(new BsFeedbackExample());
    PageInfo pageInfo = new PageInfo(list);
    log.info(JSONObject.toJSONString(pageInfo));
    return pageInfo;
}

執行sql日志:

 

5) 查詢全部數據

public PageInfo selectByExample3() {
    PageHelper.startPage(1, 0);
    List list = this.baseMapper.selectByExample(new BsFeedbackExample());
    PageInfo pageInfo = new PageInfo(list);
    log.info(JSONObject.toJSONString(pageInfo));
    return pageInfo;
}

執行sql日志:

 

6) 分頁排序orderBy

public PageInfo selectByExample5() {
    String orderBy = "id desc";
    PageHelper.startPage(1, 2, orderBy);
    List list = this.baseMapper.selectByExample(new BsFeedbackExample());
    PageInfo pageInfo = new PageInfo(list);
    log.info(JSONObject.toJSONString(pageInfo));
    return pageInfo;
}

執行sql日志:

 

7) 直接傳入分頁參數RowBounds

public PageInfo selectByExample6() {
    List list = this.baseMapper.selectByExample(new BsFeedbackExample(), new RowBounds(2, 1));
    PageInfo pageInfo = new PageInfo(list);
    log.info(JSONObject.toJSONString(pageInfo));
    return pageInfo;
}

執行sql日志:

 

 

 

4PageHelper如何確保安全性 

 

PageHelper 方法使用了靜態的 ThreadLocal 參數,分頁參數和線程是綁定的。

只要你可以保證在 PageHelper 方法調用后緊跟 MyBatis 查詢方法,這就是安全的。因為 PageHelper  finally 代碼段中自動清除了 ThreadLocal 存儲的對象。

 

如果代碼在進入 Executor 前發生異常,就會導致線程不可用,這屬於人為的 Bug(例如接口方法和 XML 中的不匹配,導致找不到 MappedStatement 時), 這種情況由於線程不可用,也不會導致 ThreadLocal 參數被錯誤的使用。

 

有一個安全性問題,需要注意一下,不然可能導致分頁錯亂。

不安全的用法:

PageHelper.startPage(1, 10);

List<Country> list;

if(param1 != null){

    list = countryMapper.selectIf(param1);

} else {

    list = new ArrayList<Country>();

}

這種情況下由於 param1 存在 null 的情況,就會導致 PageHelper 生產了一個分頁參數,但是沒有被消費,這個參數就會一直保留在這個線程上。當這個線程再次被使用時,就可能導致不該分頁的方法去消費這個分頁參數,這就產生了莫名其妙的分頁。

安全的用法:

List<Country> list;

if(param1 != null){

    PageHelper.startPage(1, 10);

    list = countryMapper.selectIf(param1);

} else {

    list = new ArrayList<Country>();

}

2. PageHelper源碼分析

1、 線程安全

PageHelper 方法使用了靜態的 ThreadLocal 參數,分頁參數和線程是綁定的。PageHelper.startPage方法調用后,將分頁參數設置在ThreadLocal中。

/**
 * 基礎分頁方法
 *
 * @author liuzh
 */
public abstract class PageMethod {
    protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
    protected static boolean DEFAULT_COUNT = true;
    /**
     * 開始分頁
     *
     * @param pageNum      頁碼
     * @param pageSize     每頁顯示數量
     * @param count        是否進行count查詢
     * @param reasonable   分頁合理化,null時用默認配置
     * @param pageSizeZero true且pageSize=0時返回全部結果,false時分頁,null時用默認配置
     */
    public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
        Page<E> page = new Page<E>(pageNum, pageSize, count);
        page.setReasonable(reasonable);
        page.setPageSizeZero(pageSizeZero);
        //當已經執行過orderBy的時候
        Page<E> oldPage = getLocalPage();
        if (oldPage != null && oldPage.isOrderByOnly()) {
            page.setOrderBy(oldPage.getOrderBy());
        }
        setLocalPage(page);
        return page;
    }

    /**
     * 開始分頁
     *
     * @param offset 頁碼
     * @param limit  每頁顯示數量
     */
    public static <E> Page<E> offsetPage(int offset, int limit) {
        return offsetPage(offset, limit, DEFAULT_COUNT);
    }

    /**
     * 開始分頁
     *
     * @param offset 頁碼
     * @param limit  每頁顯示數量
     * @param count  是否進行count查詢
     */
    public static <E> Page<E> offsetPage(int offset, int limit, boolean count) {
        Page<E> page = new Page<E>(new int[]{offset, limit}, count);
        //當已經執行過orderBy的時候
        Page<E> oldPage = getLocalPage();
        if (oldPage != null && oldPage.isOrderByOnly()) {
            page.setOrderBy(oldPage.getOrderBy());
        }
        setLocalPage(page);
        return page;
    }

    /**
     * 排序
     *
     * @param orderBy
     */
    public static void orderBy(String orderBy) {
        Page<?> page = getLocalPage();
        if (page != null) {
            page.setOrderBy(orderBy);
        } else {
            page = new Page();
            page.setOrderBy(orderBy);
            page.setOrderByOnly(true);
            setLocalPage(page);
        }
    }

2、Mybatis分頁攔截

定義了Mybatis攔截器,在Mybatis執行sql前,進行攔截,針對sql進行分頁處理;
/**
 * Mybatis - 通用分頁攔截器<br/>
 * 項目地址 : http://git.oschina.net/free/Mybatis_PageHelper
 *
 * @author liuzh/abel533/isea533
 * @version 5.0.0
 */
        @SuppressWarnings({"rawtypes", "unchecked"})
        @Intercepts(
                {
                        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
                        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
                }
        )
        public class PageInterceptor implements Interceptor {
            //緩存count查詢的ms
            protected Cache<String, MappedStatement> msCountMap = null;
            private Dialect dialect;
            private String default_dialect_class = "com.github.pagehelper.PageHelper";
            private Field additionalParametersField;
            private String countSuffix = "_COUNT";
            @Override
            public Object intercept(Invocation invocation) throws Throwable {

3、分頁參數內置查詢sql

MySqlDialect中加載AbstractHelperDialect實現,從線程中取出分頁參數,生成分頁sql;

經常提到的count查詢,其實是PageHelper幫助我們生成的一個MappedStatement內存對象,它可以免去我們在XXXMapper.xml內單獨聲明一個sql count查詢,我們只需要寫一個sql分頁業務查詢即可。
/**
 * 針對 PageHelper 的實現
 *
 * @author liuzh
 * @since 2016-12-04 14:32
 */
public abstract class AbstractHelperDialect extends AbstractDialect implements Constant {

    /**
     * 獲取分頁參數
     *
     * @param <T>
     * @return
     */
    public <T> Page<T> getLocalPage() {
        return PageHelper.getLocalPage();
    }

3.PageHelper使用建議

1、明確指定dialect(mysql)

2當業務sql存在內部嵌套時,明確編寫sql分頁業務和與它對應的count查詢,別圖省事。

MyBatis工具 http://www.mybatis.tk

推薦使用 Mybatis 通用 Mapper3 https://github.com/abel533/Mapper

推薦使用 Mybatis 分頁插件 PageHelper https://github.com/pagehelper/Mybatis-PageHelper

推薦使用Mybatis介紹 http://www.mybatis.org/mybatis-3/zh/configuration.html#plugins


免責聲明!

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



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