秒懂MyBatis分頁插件PageHelper基於ThreadLocal的實現原理分析

你或許用過mybatis,但你未必用過github上的一個基於mybatis的分頁插件PageHelper。項目地址:

https://github.com/pagehelper/Mybatis-PageHelper

小用了一下,感覺還是蠻不錯的。使用MyBatis分頁插件PageHelper非常簡單,代碼如下:

//使用方法可參考https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md
PageHelper.startPage(1, 10);
List<Country> list = countryMapper.select(null);

當看到這麼簡單的兩行代碼時,頓時勾起了我的好奇心和求知慾。兩行看似沒有任何關係的代碼,怎麼就實現分頁了呢?

文檔裏是這樣說的:“在你需要進行分頁的 MyBatis 查詢方法前調用 PageHelper.startPage 靜態方法即可,緊跟在這個方法後的第一個MyBatis 查詢方法會被進行分頁”。

哦,原來如此,如果只是爲了使用這個插件,可能看官方文檔的說明就夠了。

但是知其然還要知其所以然。我們沿着PageHelper.startPage這個靜態方法一探究竟,一步一步的深入,來到了com.github.pagehelper.page.PageMethod類裏的下面代碼:

    /**
     * 開始分頁
     *
     * @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;
    }

上面的代碼有個比較關鍵的地方:

setLocalPage(page);

setLocalPage方法是這樣的:

/**
 * 設置 Page 參數
 *
 * @param page
 */
protected static void setLocalPage(Page page) {
    LOCAL_PAGE.set(page);
}

哦,LOCAL_PAGE,這是個啥?看看它的定義是什麼鬼:

protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();

終於明白了,是基於ThreadLocal,但是還沒完,我們只看到了set的地方,卻沒有看到remove的地方,com.github.pagehelper.page.PageMethod類裏有個clearPage方法:

/**
 * 移除本地變量
 */
public static void clearPage() {
    LOCAL_PAGE.remove();
}

清除本地線程變量的就是這個clearPage方法,我們再看看這個clearPage會在什麼地方調用,看到下面的截圖,恍然大悟了。

PageInterceptor這個類的名字是不是特別熟悉?如果你自己實現過mybatis分頁插件的話,我想你會取相同的名字。我們看看這個類com.github.pagehelper.PageInterceptor的定義:

public class PageInterceptor implements Interceptor

這個類實現了org.apache.ibatis.plugin.Interceptor接口。在com.github.pagehelper.PageInterceptor.intercept(Invocation)方法的最後finally塊中調用了afterAll:

finally {
    dialect.afterAll();
}

來看看com.github.pagehelper.PageHelper中afterAll的實現,最後調用了clearPage方法清除ThreadLocal變量:

@Override
public void afterAll() {
    //這個方法即使不分頁也會被執行,所以要判斷 null
    AbstractHelperDialect delegate = autoDialect.getDelegate();
    if (delegate != null) {
        delegate.afterAll();
        autoDialect.clearDelegate();
    }
    clearPage();
}

總結起來就是,在你要使用分頁查詢的時候,先使用PageHelper.startPage這樣的語句在當前線程上下文中設置一個ThreadLocal變量,再利用mybatis提供的攔截器(插件)實現一個com.github.pagehelper.PageInterceptor接口,這個分頁攔截器攔截到後會從ThreadLocal中拿到分頁的信息,如果有分頁信息,這進行分頁查詢,最後再把ThreadLocal中的東西清除掉。

所以ThreadLocal在使用過程中一定要明白如何使用,什麼時候該清除,尤其是在線程池盛行的年代。

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