pagehelper源码解析

1,221 阅读9分钟
  1. Page,这里的Page既是一个队列,也是分页配置的元数据
//页码,从1开始
private int pageNum;
//页面大小
private int pageSize;
//起始行
private int startRow;
//末行
private int endRow;
//记录总数
private long total;
//总页数
private int pages;
//是否包含count查询
private boolean count = true;
//分页合理化,pageNum<0处理
private Boolean reasonable;
//当设置为true的时候,如果pagesize设置为0、(或RowBounds的limit=0),就不执行分页,返回全部结果
private Boolean pageSizeZero;
//进行count查询的列名
private String countColumn;
//排序
private String orderBy;
//只增加排序
private boolean orderByOnly;
//根据pageNum、pageSize、count创建page对象
Page(int pageNum, int pageSize, boolean count, Boolean reasonable);
//根据offset、limit、count创建page对象
Page(int[] rowBounds, boolean count);
//转换成PageInfo
public PageInfo<E> toPageInfo();
//转换成PageSerializable
PageSerializable<E> toPageSerializable();
//查询并返回page
public <E> Page<E> doSelectPage(ISelect select);
//查询并返回PageInfo
public <E> PageInfo<E> doSelectPageInfo(ISelect select);
//查询并返回PageSerializable
public <E> PageSerializable<E> doSelectPageSerializable(ISelect select);
//查询并返回总数
public long doCount(ISelect select);

这些变量构造了分页查询的基本属性;Page是一个队列,具有List的方法;所有的set方法都返回了page对象本身,使用了builder模式;setTotal方法根据total、pageSize计算了pages(总页数)、pageNum(页面)、startRow、endRow;setReasonable方法设置了reasonable,并且如果pageNum<=0,则将pageNum设置成1;setPageNum方法根据reasonable和pageNum设置pageNum;pageSize根据pageSize设置了pageSize、startRow、endRow;toPageInfo方法将Page转换成PageInfo;toPageSerializable方法将Page转换成PageSerializable;doSelectPage方法查询后返回Page;doSelectPageInfo方法查询后将Page转换成PageInfo后返回,这里的转换就是调用上面的toPageInfo方法;doSelectPageSerializable方法查询后将Page转换成PageSerializable后返回,这里的转换就是调用上面的toPageSerializable方法;

  1. PageSerializable,这里的PageSerializable简单抽象了查询结果
protected long total;
protected List<T> list;

total是查询记录总数,list是查询内容

  1. PageInfo,这里的PageInfo复杂抽象了抽象结果
private int pageNum;
private int pageSize;
private int size;
private int startRow;
private int endRow;
private int pages;
private int prePage;
private int nextPage;
private boolean isFirstPage = false;
private boolean isLastPage = false;
private boolean hasPreviousPage = false;
private boolean hasNextPage = false;
private int navigatePages;
private int[] navigatepageNums;
private int navigateFirstPage;
private int navigateLastPage;

PageInfo(List list, int navigatePages)方法中pageNum、pageSize、pages、size、startRow、 endRow根据参数list初始化,navigatePages是导航页码数,这里会计算navigatepageNums,navigateFirstPage、navigateLastPage、prePage、nextPage计算前后页、第一页、最后一页,isFirstPage、isLastPage、hasPreviousPage、hasNextPage计算是否为第一页、最后一页、有前一页、有下一页;

  1. PageMethod,这里的PageMethod是基础分页方法,其实就是初始化Page
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
protected static boolean DEFAULT_COUNT = true;
protected static void setLocalPage(Page page)
public static <T> Page<T> getLocalPage()
public static void clearPage()
//获取任意查询方法的count总数
public static long count(ISelect select)
//开始分页
public static <E> Page<E> startPage(Object params)
public static <E> Page<E> startPage(int pageNum, int pageSize) 
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count)
public static <E> Page<E> startPage(int pageNum, int pageSize, String orderBy)
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero)
public static <E> Page<E> offsetPage(int offset, int limit)
public static <E> Page<E> offsetPage(int offset, int limit, boolean count)
public static void orderBy(String orderBy) 

这里的startPage初始化Page,startPage方法中会获取ThreadLocal中的oldPage,如果oldPage不空则将oldPage的orderBy设置到Page上,然后将page设置到了ThreadLocal里,供后面使用

  1. Dialect,数据库方言,针对不同数据库进行实现
//跳过 count和分页查询
boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds)
//执行分页前,返回 true 会进行 count 查询,false 会继续下面的 beforePage 判断
boolean beforeCount(MappedStatement ms, Object parameterObject, RowBounds rowBounds);
//生成 count 查询 sql
String getCountSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey countKey)
//执行完 count 查询后
boolean afterCount(long count, Object parameterObject, RowBounds rowBounds)
//处理查询参数对象
Object processParameterObject(MappedStatement ms, Object parameterObject, BoundSql boundSql, CacheKey pageKey)
//执行分页前,返回 true 会进行分页查询,false 会返回默认查询结果
boolean beforePage(MappedStatement ms, Object parameterObject, RowBounds rowBounds)
//生成分页查询 sql
String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey)
//分页查询后,处理分页结果,拦截器中直接 return 该方法的返回值
Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds)
//完成所有任务后
void afterAll()
//设置参数
void setProperties(Properties properties)
  1. PageHelper,通用分页拦截器,Dialect的实现类
boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds);
public boolean beforeCount(MappedStatement ms, Object parameterObject, RowBounds rowBounds)
public String getCountSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey countKey) 
public boolean afterCount(long count, Object parameterObject, RowBounds rowBounds)
public Object processParameterObject(MappedStatement ms, Object parameterObject, BoundSql boundSql, CacheKey pageKey)
public boolean beforePage(MappedStatement ms, Object parameterObject, RowBounds rowBounds)
public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey)
public Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds)
public void afterAll()
public void setProperties(Properties properties)

这里的PageHelper继承了PageMethod,拥有count、startPage、offsetPage、orderBy方法,count方法查询记录总数,startPage、offsetPage、orderBy方法初始化Page;此外PageHelper还实现了Dialect,PageHelper拥有一个PageAutoDialect属性,实现Dialect方法都是通过PageAutoDialect完成,此处PageAutoDialect更像一个代理类,PageHelper作为PageInterceptor的Dialect默认实现类,并在插件初始化的时候调用PageHelper的setProperties方法,此时初始化PageHelper的pageParams和autoDialect属性,autoDialect的实现类是PageAutoDialect

  1. PageAutoDialect,基础方言信息
private static Map<String, Class<? extends Dialect>> dialectAliasMap
//注册各种数据库的Dialect
registerDialectAlias(String alias, Class<? extends Dialect> dialectClass)
//自动获取dialect,如果没有setProperties或setSqlUtilConfig,也可以正常进行
private boolean autoDialect = true;
//多数据源时,获取jdbcurl后是否关闭数据源
private boolean closeConn = true;
//属性配置
private Properties properties;
//缓存
private Map<String, AbstractHelperDialect> urlDialectMap
public void initDelegateDialect(MappedStatement ms)

PageAutoDialect的initDelegateDialect根据ms获取数据源,通过数据源获取数据源的url,先从缓存urlDialectMap查找,如果缓存没有则解析url中的数据库,从dialectAliasMap中找出对应的Dialect并新建对象,然后设置属性,最后将Dialect对象放入urlDialectMap中

  1. PageParams,参数信息
//RowBounds参数offset作为PageNum使用 - 默认不使用
protected boolean offsetAsPageNum = false;
//RowBounds是否进行count查询 - 默认不查询
protected boolean rowBoundsWithCount = false;
//当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果
protected boolean pageSizeZero = false;
//分页合理化
protected boolean reasonable = false;
//是否支持接口参数来传递分页参数,默认false
protected boolean supportMethodsArguments = false;
//默认count(0)
protected String countColumn = "0";

这里的PageParams主要作为获取Page对象使用,offsetAsPageNum设置为true或者false,主要的区别就是当获取ThreadLocal中的page为空构造page的时候,使用offset作为pageNum;rowBoundsWithCount初始化Page的count属性;supportMethodsArguments设置为true表示可以从参数中获取Page;当Page的reasonable和pageSizeZero为空时使用PageParams的reasonable和pageSizeZero;

  1. CountSqlParser,解析sql并将sql中的order by去掉

  2. OrderByParser,将sql中的orderby去掉并添加参数orderby字段

  3. PageInterceptor,sql查询的切面

public Object intercept(Invocation invocation) throws Throwable {
    try {
        //调用方法判断是否需要进行分页,如果不需要,直接返回结果
        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();
        }
    }
}

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;
}

代码中首先通过skip判断是否需要插件分页处理;通过beforeCount判断是否需要进行count查询;count方法首先通过ExecutorUtil.getExistedMappedStatement判断是否存在手写的count查询,如果有则执行ExecutorUtil.executeManualCount,如果没有则从缓存msCountMap里获取count查询语句,如果缓存里没有count查询语句,则创建一个count查询语句,执行ExecutorUtil.executeAutoCount;通过afterCount判断是否需要进行分页查询,如果需要,执行ExecutorUtil.pageQuery,afterCount方法设置page的total;afterPage方法设置page的结果;

  1. AbstractHelperDialect,Dialect的抽象类
//此方法已经被PageHelper重写
public final boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
        //该方法不会被调用
        return true;
    }
//beforeCount判断是否需要count查询,PageHelper调用此方法
public boolean beforeCount(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
    Page page = getLocalPage();
    return !page.isOrderByOnly() && page.isCount();
}
//获取count查询语句,PageHelper调用此方法,ExecutorUtil的executeAutoCount会调用
public String getCountSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey countKey) {
    Page<Object> page = getLocalPage();
    String countColumn = page.getCountColumn();
    if (StringUtil.isNotEmpty(countColumn)) {
        return countSqlParser.getSmartCountSql(boundSql.getSql(), countColumn);
    }
    return countSqlParser.getSmartCountSql(boundSql.getSql());
}
//设置page的total,并判断是否需要分页查询,PageHelper调用此方法
public boolean afterCount(long count, Object parameterObject, RowBounds rowBounds) {
    Page page = getLocalPage();
    page.setTotal(count);
    if (rowBounds instanceof PageRowBounds) {
        ((PageRowBounds) rowBounds).setTotal(count);
    }
    //pageSize < 0 的时候,不执行分页查询
    //pageSize = 0 的时候,还需要执行后续查询,但是不会分页
    if (page.getPageSize() < 0) {
        return false;
    }
    //count肯定大于前面页的数据
    return count > ((page.getPageNum() - 1) * page.getPageSize());
}
//判断是否需要分页查询,PageHelper调用此方法,ExecutorUtil的pageQuery会调用
public boolean beforePage(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
    Page page = getLocalPage();
    if (page.isOrderByOnly() || page.getPageSize() > 0) {
        return true;
    }
    return false;
}
//获取分页查询语句,PageHelper调用此方法,ExecutorUtil的pageQuery会调用
public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
    String sql = boundSql.getSql();
    Page page = getLocalPage();
    //支持 order by
    String orderBy = page.getOrderBy();
    if (StringUtil.isNotEmpty(orderBy)) {
        pageKey.update(orderBy);
        sql = OrderByParser.converToOrderBySql(sql, orderBy);
    }
    if (page.isOrderByOnly()) {
        return sql;
    }
    return getPageSql(sql, page, pageKey);
}
//设置page的查询结果和total,PageHelper调用此方法
public Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds) {
    Page page = getLocalPage();
    if (page == null) {
        return pageList;
    }
    page.addAll(pageList);
    if (!page.isCount()) {
        page.setTotal(-1);
    } else if ((page.getPageSizeZero() != null && page.getPageSizeZero()) && page.getPageSize() == 0) {
        page.setTotal(pageList.size());
    } else if(page.isOrderByOnly()){
        page.setTotal(pageList.size());
    }
    return page;
}
//处理分页查询的参数,PageHelper调用此方法,ExecutorUtil的pageQuery会调用
public Object processParameterObject(MappedStatement ms, Object parameterObject, BoundSql boundSql, CacheKey pageKey){
    
}
  1. MySqlDialect,AbstractHelperDialect的mysql实现类,实现了processPageParameter和getPageSql方法,这两个方法都是在AbstractHelperDialect中定义的模板方法
//分页查询参数
public Object processPageParameter(MappedStatement ms, Map<String, Object> paramMap, Page page, BoundSql boundSql, CacheKey pageKey) {
    paramMap.put(PAGEPARAMETER_FIRST, page.getStartRow());
    paramMap.put(PAGEPARAMETER_SECOND, page.getPageSize());
    //处理pageKey
    pageKey.update(page.getStartRow());
    pageKey.update(page.getPageSize());
    //处理参数配置
    if (boundSql.getParameterMappings() != null) {
        List<ParameterMapping> newParameterMappings = new ArrayList<ParameterMapping>(boundSql.getParameterMappings());
        if (page.getStartRow() == 0) {
            newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_SECOND, Integer.class).build());
        } else {
            newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_FIRST, Integer.class).build());
            newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_SECOND, Integer.class).build());
        }
        MetaObject metaObject = MetaObjectUtil.forObject(boundSql);
        metaObject.setValue("parameterMappings", newParameterMappings);
    }
    return paramMap;
}
//使用mysql的分页方法
public String getPageSql(String sql, Page page, CacheKey pageKey) {
    StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
    sqlBuilder.append(sql);
    if (page.getStartRow() == 0) {
        sqlBuilder.append(" LIMIT ? ");
    } else {
        sqlBuilder.append(" LIMIT ?, ? ");
    }
    return sqlBuilder.toString();
}
  1. ExecutorUtil,分页查询的工具类
//尝试获取已经存在的在 MS,提供对手写count和page的支持
public static MappedStatement getExistedMappedStatement(Configuration configuration, String msId) 
//执行手动设置的 count 查询,该查询支持的参数必须和被分页的方法相同
public static Long executeManualCount(Executor executor, MappedStatement countMs,
Object parameter, BoundSql boundSql,
ResultHandler resultHandler)
//执行自动生成的 count 查询
public static Long executeAutoCount(Dialect dialect, Executor executor, MappedStatement countMs,Object parameter, BoundSql boundSql,RowBounds rowBounds, ResultHandler resultHandler)
//分页查询
public static  <E> List<E> pageQuery(Dialect dialect, Executor executor, MappedStatement ms, Object parameter,RowBounds rowBounds, ResultHandler resultHandler,
 BoundSql boundSql, CacheKey cacheKey)