MyBatis分页插件PageHelper实现原理分析

项目地址:https://github.com/pagehelper/Mybatis-PageHelper

 使用方法

public PageInfo<User> findAllByLike(String searchText, PageInfo pageInfo) {
    PageHelper.startPage(pageInfo.getPageNum(),pageInfo.getPageSize());
    List<User> userList = userDao.findAllByLikeNickName(searchText);
    PageInfo<User> page = new PageInfo<>(userList);
    return page;
}

我心想着为什么这一句话就可以实现分页呢,况且sql语句也没有按照分页的执行呀。官方文档里是这样说的:“在你需要进行分页的 MyBatis 查询方法前调用 PageHelper.startPage 静态方法即可,紧跟在这个方法后的第一个MyBatis 查询方法会被进行分页”。哦,原来如此,如果只是为了使用这个插件,可能看官方文档的说明就够了。但是知其然还要知其所以然。我们沿着PageHelper.startPage这个静态方法一探究竟,一步一步的深入,来到了com.github.pagehelper.page.PageMethod类里的下面代码:

public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
    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);
        return page;
}

里面最重要的就是这个setLocalPage(page);

protected static void setLocalPage(Page page) {
    LOCAL_PAGE.set(page);
}

终于明白了,是基于ThreadLocal,但是还没完,我们只看到了set的地方,却没有看到remove的地方,com.github.pagehelper.page.PageMethod类里有个clearPage方法

清除本地线程变量的就是这个clearPage方法,我们再看看这个clearPage会在什么地方调用,看到下面的截图,恍然大悟了。PageInterceptor这个类的名字是不是特别熟悉?如果你自己实现过mybatis分页插件的话,我想你会取相同的名字。我们看看这个类com.github.pagehelper.PageInterceptor的定义:

这个类实现了org.apache.ibatis.plugin.Interceptor接口。在com.github.pagehelper.PageInterceptor.intercept(Invocation)方法的最后finally块中调用了afterAll

来看看com.github.pagehelper.PageHelper中afterAll的实现,最后调用了clearPage方法清除ThreadLocal变量:

总结起来就是,在你要使用分页查询的时候,先使用PageHelper.startPage这样的语句在当前线程上下文中设置一个ThreadLocal变量,再利用mybatis提供的拦截器(插件)实现一个com.github.pagehelper.PageInterceptor接口,这个分页拦截器拦截到后会从ThreadLocal中拿到分页的信息,如果有分页信息,这进行分页查询,最后再把ThreadLocal中的东西清除掉。所ThreadLocal在使用过程中一定要明白如何使用,什么时候该清除,尤其是在线程池盛行的年代。

猜你喜欢

转载自blog.csdn.net/tianyucxh/article/details/103131239