Mybatis 分页组件PageHelper 使用
PageHelper 官网:
- https://pagehelper.github.io
- 关于PageHelper 的开发和原理官网上也已经讲的很明确了,这里不过多解析官网的意思
快速构建:
- 首先需要一个 SSM 项目【也可以单体Mybatis】
新增Maven 配置:
com.github.pagehelper
pagehelper
5.1.10
mybatis-config.xml 加入以下配置【也可以写在Spring的配置中,写法略有不同】:
Dao 以及 Xml 写法。
// Dao
ListgetListByPage(); <!— XML —>
重点Spring 的实现类:
/**
* 分页查询数据
* @return
*/
public PageInfo getListByPage(PageInfo pageInfo){
//设置分页信息(第几页,每页数量),并将Page 对象放入到 ThreadLocal 中,
PageHelper.startPage(pageInfo.getPageNum(), pageInfo.getPageSize());
//查询结果,我们的拦截器会在这里起作用
List<ClassInfo> result = classDao.getListByPage();
// 生成返回结果
PageInfo<ClassInfo> pageInfoResult = new PageInfo<ClassInfo>(result);
return pageInfoResult;
}
这里需要注意的是,getListByPage 的参数PageInfo 仅仅由于接受 前端分页参数,不可用于返回。
- 通过Controller执行我们的代码,看一下Mybatis sql打印结果:
哦吼,为什么会打印出这两条SQL ,一条Count,一条Select,我原来的Sql 为什么没有执行那,不着急我们看下一部分
源码浅析:
要想真正理解这个功能,需要对Mybatis源码 和 Mybatis拦截器有一定的了解,那么PageInterceptor中究竟做了什么那,直接上源码,因为它是中国人开发的,代码注释也一目了然,完全Copy
package com.github.pagehelper;
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";
@Override
public Object intercept(Invocation invocation) throws Throwable {
try {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
Executor executor = (Executor) invocation.getTarget();
CacheKey cacheKey;
BoundSql boundSql;
//由于逻辑关系,只会进入一次
if (args.length == 4) {
//4 个参数时
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
//6 个参数时
cacheKey = (CacheKey) args[4];
boundSql = (BoundSql) args[5];
}
checkDialectExists();
List resultList;
//调用方法判断是否需要进行分页,如果不需要,直接返回结果
if (!dialect.skip(ms, parameter, rowBounds)) {
//判断是否需要进行 count 查询
if (dialect.beforeCount(ms, parameter, rowBounds)) {
//查询总数
Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql);
//处理查询总数,返回 true 时继续分页查询,false 时直接返回
if (!dialect.afterCount(count, parameter, rowBounds)) {
//当查询总数为 0 时,直接返回空的结果
return dialect.afterPage(new ArrayList(), parameter, rowBounds);
}
}
resultList = ExecutorUtil.pageQuery(dialect, executor,
ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
} else {
//rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
return dialect.afterPage(resultList, parameter, rowBounds);
} finally {
if(dialect != null){
dialect.afterAll();
}
}
}
/**
* Spring bean 方式配置时,如果没有配置属性就不会执行下面的 setProperties 方法,就不会初始化
* <p>
* 因此这里会出现 null 的情况 fixed #26
*/
private void checkDialectExists() {
if (dialect == null) {
synchronized (default_dialect_class) {
if (dialect == null) {
setProperties(new Properties());
}
}
}
}
private Long count(Executor executor, MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) throws SQLException {
String countMsId = ms.getId() + countSuffix;
Long count;
//先判断是否存在手写的 count 查询
MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);
if (countMs != null) {
count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
} else {
countMs = msCountMap.get(countMsId);
//自动创建
if (countMs == null) {
//根据当前的 ms 创建一个返回值为 Long 类型的 ms
countMs = MSUtils.newCountMappedStatement(ms, countMsId);
msCountMap.put(countMsId, countMs);
}
count = ExecutorUtil.executeAutoCount(dialect, executor, countMs, parameter, boundSql, rowBounds, resultHandler);
}
return count;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
//缓存 count ms
msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);
String dialectClass = properties.getProperty("dialect");
if (StringUtil.isEmpty(dialectClass)) {
dialectClass = default_dialect_class;
}
try {
Class<?> aClass = Class.forName(dialectClass);
dialect = (Dialect) aClass.newInstance();
} catch (Exception e) {
throw new PageException(e);
}
dialect.setProperties(properties);
String countSuffix = properties.getProperty("countSuffix");
if (StringUtil.isNotEmpty(countSuffix)) {
this.countSuffix = countSuffix;
}
}
}
Mybatis 执行 Dao层方法时执行时,会被拦截器拦截执行Interceptor.intercept()方法,我们可以再源码中看到 第37行时查询了总条数,在第44行执行了 分页查询,也就是说PageInterceptor会拦截请求,找到我们本来要执行的Sql并将其拆解,生成 count 和 Select Limit Sql 执行,并最后将结果返回。
- 那么为什么我们原来的XML 配置的sql 并没有执行那,是因为Mybatis执行XML中的sql 也是一个拦截器,Mybatis执行拦截器是责任链的形式,而Mybatis自己的拦截器优先级最低,先执行了 PageInterceptor,并且PageInterceptor 中没有调用 Mybatis本身的拦截器,所以就没有执行。需要在PageInterceptor.intercept() 中加入代码: Object result = invocation.proceed(); 程序才会回去执行下一级拦截器,显然这里根本不需要执行下一个拦截器
还没有评论,来说两句吧...