pageHelper作为Mybatis最好用的分页插件,自然受到极大多数人的追捧。而这里想要尽量阐述清楚pageHelper的具体使用步骤,实现的背后原理,以及与原始分页写法相比有什么优缺点等。
一、使用步骤
在这里,使用SpringBoot来集成pageHelper作为演示。
- 引入Maven依赖
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
- 查询所有记录的SQL
- 在控制层加入PageHelper,表示查前十条
PageInfo<User> getList(User user){
PageHelper.startPage(1, 10);
return new PageInfo<User>(userService.getList(user));
}
至此,基本的pageHelper分页使用就是这样,后面可以自己再做些封装等。
二、分页原理
通过第一部分的应用实战,我们在返回结果中可以看到total总数,以及pageNum、pageSize等其他分页属性,这些属性的结果都是怎么返回的呢,PageHelper.startPage()这行代码背后又做了哪些事情呢。
1. 统计总数
在mapper的xml文件中,我并没有写统计总数的count方法,但是pageHelper能够自动计算出来。经过分析是因为默认在列表查询的方法前包装一个select count(1),且select的id为xxxx_COUNT,xxxx为查询List的id,即效果类似于:
<select id="getPageList" resultMap="BaseResultMap" >
select id, name from user
</select>
<select id="getPageList_COUNT" resultType="Long" >
select count(1) from user
</select>
2. 源码分析
当使用PageHelper.startPage(1, 10);的时候,我们通过查看源码发现,分页参数通过传递达到了PageHelper的父类PageMethod的startPage()方法。
有多个startPage()重载方法,且经过观察发现,所有的startPage()均指向public static Page startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero){},目的是为了构造一个Page对象,且作为ArrayList的子类,最终返回构造好的Page对象:
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal();
protected static boolean DEFAULT_COUNT = true;
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;
}
public class Page<E> extends ArrayList<E> implements Closeable {
private static final long serialVersionUID = 1L;
private int pageNum;
private int pageSize;
private long startRow;
private long endRow;
private long total;
private int pages;
private boolean count;
private Boolean reasonable;
private Boolean pageSizeZero;
private String countColumn;
private String orderBy;
private boolean orderByOnly;
private BoundSqlInterceptor boundSqlInterceptor;
private transient Chain chain;
...
}
分页参数是跟ThreadLocal本地线程绑定的,确保当前线程中有一个Page对象。
由于是在SpringBoot中,所以我们找到PageHelper的自动配置类PageHelperAutoConfiguration
其中,addPageInterceptor()方法主要加载了配置文件中我们自定义的属性,以及PageInterceptor拦截器的实例化。
通过这个拦截器即可在数据库操作阶段进行切入操作,我们进入到PageInterceptor中查看实现过程,其中PageInterceptor实现了Interceptor接口
这里的count就是统计总数的实现。其中主要方法是通过Dialect这个接口来实现的,由于数据库类型不一致所以实现类和方法均有差异,故可以看到实现类有很多:
PageInterceptor中使用的Dialect默认实现类是PageHelper,在PageHelper中使用的具体实现类其实都交给了PageAutoDialect。
最后回到PageInterceptor的intercept()方法中,
ExecutorUtil.pageQuery(this.dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
具体的分页查询实现就在ExecutorUtil工具类的pageQuery()方法
三、总结
最开始对一句代码就能进行分页表示好奇,通过查阅相关资料了解源码实现后对整个pageHelper有了大概的理解。不过也只是了解了一点皮毛,更多的是对源码分析敲砖。
参考资料: