介绍
PageHelper是一个很好的mybatis分页插件。经过简单的配置,只需要几行代码就可以实现分页查询。
数据库数据如下:
@RequestMapping("/findAllUser")
public ResponseResult
PageHelper.startPage(pageModel.getPageNum(), pageModel.getPageSize());
PageHelper.orderBy("id desc");
List
PageBean
return new ResponseResult<>(200, "success", pageBean);
}
代码看起来比较奇怪,只需要传递进来需要的当前页面数已经每页的大小就可以实现对mybatis的查询语句分页;另外还可以实现升序和降序。
- 它是如何办到的?是否使用了动态代理?
- 是通过sql实现分页吗?
- PageHelper.startPage()是不是使用了ThreadLocal?什么时候释放ThreadLocal里面的value?是否会造成ThreadLocal内存泄漏?
带着这些问题我进行了源码分析,本文不介绍如何配置实现分页功能,代码可以从这里下载: https://github.com/pmh905001/freedom-20200203springboot
主要还是探究PageHelper的实现原理,可以从这篇文章借鉴到源码分析的一些思路.
入手
如何入手分析PageHelper的实现原理呢?
当前代码是controller--->service--->Mapper就简单几行代码是无法看到跟踪到内部实现的,排除此方法。
它的代码里面既然设置了pageSize, PageHelper.startPage(pageModel.getPageNum(), pageModel.getPageSize());那么就一定有使用它的地方,我们顺着这个方法可以跟踪到这里
PageHelper原理分析
看到这里实际上回答我的第三个问题:PageHelper.startPage()是不是使用了ThreadLocal?是的,它是使用了ThreadLocal。
那么我们就在我们就在PageSize这个字段做一个跟踪断点。我们可以得到如下调用栈:
我们可以找到一个名为PageInterceptor的代理类对正在执行的mybatis SQL进行了拦截,首先查询的总数,然后在进行分页查询。
并且从下面的语句我们可以看出这个动态代理类拦截的是Executor类里面的query方法。
分析到这里,实际上已经回答了我的第一个疑问 。
- 它是如何办到的?是否使用了动态代理?
他是通过动态代理拦截了Executor.query()方法。而且他应该java自带的动态代理实现。
通过分析PageInterceptor,我们同样可以回答问题2:
2.是通过sql实现分页吗?
是通过sql来实现分页的,这里不再详述。
其他
还有一个问题是我们看到了PageHelper是通过ThreadLocal来设置分页信息的,那么什么地方该来释放分页信息呢?否则有可能造成内存泄漏.
PageHelper里面有一个clearPage()方法,反向查看调用点即可以知道程序在哪儿释放分页信息。
从上面的代码可以看出,拦截器执行完毕之后最终会释放分页信息。并且放在finally块中,可以保证不会出现ThreadLocal内存泄漏。
还有一个问题,Mybatis是否支持Executor多个拦截器,比如:分页拦截器和排序拦截器,答案是肯定的。Mybatis使用提供了一种插件的机制,读者可以参考一下InterceptorChain.pluginAll()方法。
这里代码有点绕,我之前看到这里感觉很别扭,这应该只返回一个动态代理类,怎么会支持多层拦截呢? 实际上这里代码写的很精炼,每一次循环相当于被代理上面套了一层,第二次循环就会再在动态代理类基础上再封装一层.