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";
...
}