PageHelper實現方式?
- PageHelper首先將前端傳遞的參數保存到page這個對象中,接着將page的副本存放入ThreadLoacl中,這樣可以保證分頁的時候,參數互不影響。
- 接着利用了mybatis提供的攔截器,取得ThreadLocal的值,重新拼裝分頁SQL,執行查詢的時候通過攔截器在sql語句中添加分頁參數,之後實現分頁查詢。
- 最後在finally代碼段中自動清除了ThreadLocal存儲的對象,防止內存泄漏。
第一步:在ThreadLocal中保存Page
public abstract class PageMethod {
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal();
...
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
//page對象存儲瞭如pageNum、pageSize、orderBy等查詢條件
Page<E> page = new Page(pageNum, pageSize, count);
page.setReasonable(reasonable);
page.setPageSizeZero(pageSizeZero);
Page<E> oldPage = getLocalPage();
if (oldPage != null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
setLocalPage(page);//存儲到ThreadLocal中
return page;
}
protected static void setLocalPage(Page page) {
LOCAL_PAGE.set(page);
}
Debug到這個位置,其實Page.startpage已經完成了它應該做的,接下來就是執行selectByPrimaryKey()
這個方法了。
第二步:SqlSessionFactory注入Interceptor
繼續Debug下一句selectByPrimaryKey()
,我們來到了這個類:
public class MapperProxy<T> implements InvocationHandler, Serializable {
...
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}
}
先不管來到的哪個方法,光是看InvocationHandler就能明白,這是個動態代理、。
在這個類裏,Mybatis完成了初始化的過程:
初始化的的核心流程就是 讀取配置文件 到 Congiguration實例,之後生成全局公用的 SqlSessionTemplate 以SqlSessionFactory實例。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
...
private Interceptor[] plugins;
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
...
if (!ObjectUtils.isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach((plugin) -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> {
return "Registered plugin: '" + plugin + "'";
});
});
}
...
}
}
在SqlSessionFactoryBean中,我們注意到,其屬性包含了Interceptor數組,而buildSqlSessionFactory()
方法通過在Configuration加入plugins,完成Interceptor的注入。
初始化完成後,接下來是Mapper的查詢流程,是一個調用鏈,如下:
核心是 configuration.newExecutor()方法,會加載攔截鏈,也就是pageInterceptor。
第三步:PageInterceptor實現分頁
PageHelper最核心的邏輯在 PageInterceptor 中,PageInterceptor 是一個攔截器。
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";
...
}