PageHelper分页原理浅析

PageHelper分页原理浅析

众所周知,PageHelper的使用要在查询前调用一下startPage()方法,然后接下来的第一个查询就会返回分页结果。

PageHelper.startPage(1, 10);
List<Country> list = countryMapper.selectIf(1);

为什么在一个Mybatis发起查询前写个startPage()方法,这个查询就可以分页呢?而且只影响了一次查询,后续的查询都是普通的查询,不会分页。

来看看startPage()里都干了什么

跟进去发现setLocalPage(page);

再跟进去发现原来是一个ThreadLocal,往里面先放进去一个page,要用的时候随时可以取出来,不用一直带着,优雅。

什么是ThreadLocal,请自行百度,先简单理解的话就是一个当前线程内存放数据的变量。而且这里边存放的东西是线程隔离的,其他线程拿不到,而当前线程是可以随时拿到里面的数据。

那放进去,总得拿出来吧,在哪里拿出来的呢?先放答案,这一切都是拦截器PageInterceptor在工作。跟进去代码就知道是AbstractHelperDialect的aftercont()和afterPage()方法,一个为page塞入总数,一个塞入分页数据并返回最终的page。

可以看到是PageHelper的核心拦截器PageInterceptor里,PageInterceptor的分页逻辑中调用了这2个方法。

调用一次分页一次原理

同时到这里可以回答前面的问题,为什么PageHelper只处理一次请求内(一个主线程内)紧跟着startPage()方法后的查询进行分页,后面的查询则不再进行分页。

看看,处理完aftercont()和afterPage()方法后,有个finally操作,跟进去看到:

查询完就把线程内(前面提到的) ThreadLocal<Page> LOCAL_PAGE 存储的数据移除了。

那下一次的查询也就不会进入分页逻辑,直接跳过了分页逻辑返回原生执行器的查询结果。

跟进去可以看到跳过的判断:第二次查询在ThreadLocal<Page> LOCAL_PAGE获取不到page,已经在上一次发分页查询中的finally中清除,已经是null,则返回true跳过分页逻辑。

这就是大概的实现和原理了,那继续往下纠结的话就得看看PageHelper和Mybatis的关系了。

mybatis插件-拦截器

首先PageHelper是mybatis的插件,mybatis的插件其实都是mybatis执行方法时的拦截器。也就是说mybatis的插件都是一个个的拦截器.,前面提到的PageInterceptor就是了。

mybatis允许用户去通过插件的方式去增强mybatis的能力。

Mybatis拦截器只能拦截四种类型的接口:ExecutorStatementHandlerParameterHandlerResultSetHandler。这是在Mybatis的Configuration中写死了的,,如果要支持拦截其他接口就需要我们重写Mybatis的Configuration,不过最好不要。

首先要知道mybatis插件拦截原生的以上四种接口,拦截下来的目的是为了增强它。

那么他是在哪里拦截的呢?拦截了什么呢?

那我们聊的PageHelper拦截的就是mybatis的执行器Executor里的query方法,query方法是所有查询语句的最终归宿。

拦截下来后时候查一下分页,查一下总数,分页Page也就组装成了。

拦截过程

mybatis所有的操作都得先从获得Sqlsession开始

跟到实现类DefaultSqlSessionFactory发现 执行器Executor在这里被初始化。

而插件的装载也就在初始化过程中执行,将原生的Executor传入,最终还是返回被增插件强过后的Executor

被插件增强,前面提到,其实就是被拦截器InterceptorChain增强。

InterceptorChain拦截器责任链(mybatis在加载配置时已经装载,这里不再展开)

再跟过去果不其然是个动态代理

getSignatureMap方法可以拿到拦截器需要拦截的mybatis的一些默认执行方法,配置拦截器时用@Intercepts的@Signature属性。

上面的逻辑将配置了@Intercepts注解的@Signature属性里的方法拿到进行拦截,如果是注解里包含的方法就拦截。

举个例子:一个简单的拦截器(插件) query方法

@Intercepts({@Signature(type = Executor.class,method = "query",args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})})
public class MyPlugin implements Interceptor {

    /**
     * 这个方法会直接覆盖原有方法
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("成功拦截了Executor的query方法,在这里我可以做点什么");
       //do something 增强后
         return invocation.proceed();//调用原方法
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target,this);//把被拦截对象生成一个代理对象
    }

    @Override
    public void setProperties(Properties properties) {//可以自定义一些属性
        System.out.println("自定义属性:userName->" + properties.getProperty("userName"));
    }

或者PageHelper插件 PageInterceptor 同样也是query方法

可以看到,当注解里method="query"时,也就是当原生的Executor执行query方法时(实际是拦截 了2个query方法),动态代理就会介入然后进行增强:执行完原生query方法后,执行拦截器里的增强方法intercept()。也就是分页逻辑的入口。

如果拿到了就执行拦截器的拦截方法

如果对JDK的动态代理是个啥都不知道,建议还是要去了解一下。什么是代理模式,静态代理,动态代理,JDK默认实现的动态代理和Spring实现的code genertate lib (cglib)代理有啥不同。可以说动态代理就是Spring Aop的基石。

不想了解也可以,简单说的话就是增强,增强某个类的能力,而且是不知不觉中。被增强了是因为被人代理了,被代理了也就是被人在切面完成了任务。想想这动态代理和aop的微妙关系。

好了暂时就写那么多了,能力有限。

  • 15
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值