你或許用過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在使用過程中一定要明白如何使用,什麼時候該清除,尤其是在線程池盛行的年代。