pagehelper 分页插件

发布时间 2024-01-06 10:44:34作者: YangDanMua
public class PageHelper extends PageMethod implements Dialect, BoundSqlInterceptor.Chain {

PageMethod

分页静态方法

public abstract class PageMethod {
  protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();

  public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
    Page<E> page = new Page<E>(pageNum, pageSize, count);
    page.setReasonable(reasonable);
    page.setPageSizeZero(pageSizeZero);
    //当已经执行过orderBy的时候
    Page<E> oldPage = getLocalPage();
    if (oldPage != null && oldPage.isOrderByOnly()) {
      page.setOrderBy(oldPage.getOrderBy());
    }
    setLocalPage(page);
    return page;
  }

  public static void orderBy(String orderBy) {
    Page<?> page = getLocalPage();
    if (page != null) {
      page.setOrderBy(orderBy);
    } else {
      page = new Page();
      page.setOrderBy(orderBy);
      page.setOrderByOnly(true);
      setLocalPage(page);
    }
  }

  public static <E> Page<E> offsetPage(int offset, int limit, boolean count) {
    Page<E> page = new Page<E>(new int[]{offset, limit}, count);
    //当已经执行过orderBy的时候
    Page<E> oldPage = getLocalPage();
    if (oldPage != null && oldPage.isOrderByOnly()) {
      page.setOrderBy(oldPage.getOrderBy());
    }
    setLocalPage(page);
    return page;
  }
}

分页参数存储依赖于

  • Page 分页对象
  • ThreadLocal 保存分页对象

BoundSqlInterceptor.Chain

BoundSqlInterceptor 是处理 BoundSql 的
BoundSqlInterceptor.Chain 则是一个处理器链

public interface BoundSqlInterceptor {
  BoundSql boundSql(Type type, BoundSql boundSql, CacheKey cacheKey, Chain chain);
}
enum Type {
  /**
   * 原始SQL,分页插件执行前,先执行这个类型
   */
  ORIGINAL,
  /**
   * count SQL,第二个执行这里
   */
  COUNT_SQL,
  /**
   * 分页 SQL,最后执行这里
   */
  PAGE_SQL
}
interface Chain {

  Chain DO_NOTHING = new Chain() {
    @Override
    public BoundSql doBoundSql(Type type, BoundSql boundSql, CacheKey cacheKey) {
      return boundSql;
    }
  };

  BoundSql doBoundSql(Type type, BoundSql boundSql, CacheKey cacheKey);

}

Dialect

数据库分页方言

public interface Dialect {
  // 跳过 count 和 分页查询, true 跳过,返回默认查询结果,false 执行分页查询
  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 查询后,true 继续分页查询,false 直接返回
  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);
}

PageHelper

public class PageHelper {
  // 分页参数信息
  private PageParams pageParams;
  // 分页方言
  private PageAutoDialect autoDialect;
  // BoundSql 拦截
  private PageBoundSqlInterceptors pageBoundSqlInterceptors;
}
// 是否跳过分页
public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
  Page page = pageParams.getPage(parameterObject, rowBounds);
  // 没有分页参数则跳过分页
  if (page == null) {
    return true;
  } else {
    //设置默认的 count 列
    if (StringUtil.isEmpty(page.getCountColumn())) {
      page.setCountColumn(pageParams.getCountColumn());
    }
    // ?
    autoDialect.initDelegateDialect(ms, page.getDialectClass());
    return false;
  }
}

// 分页查询后处理分页结果, 一般就是转换 List 为分页对象, 填充分页参数数据
@Override
public Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds) {
  //这个方法即使不分页也会被执行,所以要判断 null
  AbstractHelperDialect delegate = autoDialect.getDelegate();
  if (delegate != null) {
    return delegate.afterPage(pageList, parameterObject, rowBounds);
  }
  return pageList;
}

// 完成所有任务后
@Override
public void afterAll() {
  //这个方法即使不分页也会被执行,所以要判断 null
  AbstractHelperDialect delegate = autoDialect.getDelegate();
  if (delegate != null) {
    // 暂无实现
    delegate.afterAll();
    // 也有 ThreadLocal 的清理
    autoDialect.clearDelegate();
  }
  // 清除 ThreadLocal 的分页参数
  clearPage();
}

@Override
public BoundSql doBoundSql(BoundSqlInterceptor.Type type, BoundSql boundSql, CacheKey cacheKey) {
  Page<Object> localPage = getLocalPage();
  BoundSqlInterceptor.Chain chain = localPage != null ? localPage.getChain() : null;
  if (chain == null) {
    BoundSqlInterceptor boundSqlInterceptor = localPage != null ? localPage.getBoundSqlInterceptor() : null;
    BoundSqlInterceptor.Chain defaultChain = pageBoundSqlInterceptors != null ? pageBoundSqlInterceptors.getChain() : null;
    if (boundSqlInterceptor != null) {
      chain = new BoundSqlInterceptorChain(defaultChain, Arrays.asList(boundSqlInterceptor));
    } else if (defaultChain != null) {
      chain = defaultChain;
    }
    if (chain == null) {
      chain = DO_NOTHING;
    }
    if (localPage != null) {
      localPage.setChain(chain);
    }
  }
  return chain.doBoundSql(type, boundSql, cacheKey);
}

@Override
public void setProperties(Properties properties) {
  setStaticProperties(properties);
  pageParams = new PageParams();
  autoDialect = new PageAutoDialect();
  pageBoundSqlInterceptors = new PageBoundSqlInterceptors();
  pageParams.setProperties(properties);
  autoDialect.setProperties(properties);
  pageBoundSqlInterceptors.setProperties(properties);
  //20180902新增 aggregateFunctions, 允许手动添加聚合函数(影响行数)
  CountSqlParser.addAggregateFunctions(properties.getProperty("aggregateFunctions"));
}

PageInterceptor

分页插件,拦截 Executor 执行器的 query 方法

@Intercepts(
  {
    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
  }
)
public class PageInterceptor implements Interceptor {
public class PageInterceptor implements Interceptor {
  private static final Log                            log                   = LogFactory.getLog(PageInterceptor.class);
  private static       boolean                        debug                 = false;
  protected            Cache<String, MappedStatement> msCountMap            = null;
  protected            CountMsIdGen                   countMsIdGen          = CountMsIdGen.DEFAULT;
  private volatile     Dialect                        dialect;
  private              String                         countSuffix           = "_COUNT";
  private              String                         default_dialect_class = "com.github.pagehelper.PageHelper";

  public PageInterceptor() {
      // Banner 打印
  }
}
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();
    //对 boundSql 的拦截处理
    if (dialect instanceof BoundSqlInterceptor.Chain) {
      // ? 一般是做啥呢
      boundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.ORIGINAL, boundSql, cacheKey);
    }
    List resultList;
    //调用方法判断是否需要进行分页,如果不需要,直接返回结果
    // false 需要分页
    if (!dialect.skip(ms, parameter, rowBounds)) {
      //开启debug时,输出触发当前分页执行时的PageHelper调用堆栈
      // 如果和当前调用堆栈不一致,说明在启用分页后没有消费,当前线程再次执行时消费,调用堆栈显示的方法使用不安全
      debugStackTraceLog();
      //判断是否需要进行 count 查询
      if (dialect.beforeCount(ms, parameter, rowBounds)) {
        //查询总数
        Long count = count(executor, ms, parameter, rowBounds, null, 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) {
      // finally 执行清除分页状态信息, 典型 ThreadLocal 的数据
      dialect.afterAll();
    }
  }
}

ExecutorUtil

执行 count 和 分页查询

public static <E> List<E> pageQuery(Dialect dialect, Executor executor, MappedStatement ms, Object parameter,
                  RowBounds rowBounds, ResultHandler resultHandler,
                  BoundSql boundSql, CacheKey cacheKey) throws SQLException {
  //判断是否需要进行分页查询
  if (dialect.beforePage(ms, parameter, rowBounds)) {
    //生成分页的缓存 key
    CacheKey pageKey = cacheKey;
    //处理参数对象
    parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);
    //调用方言获取分页 sql
    String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
    BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);

    Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
    //设置动态参数
    for (String key : additionalParameters.keySet()) {
      pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
    }
    //对 boundSql 的拦截处理
    if (dialect instanceof BoundSqlInterceptor.Chain) {
      pageBoundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.PAGE_SQL, pageBoundSql, pageKey);
    }
    //执行分页查询
    return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
  } else {
    //不执行分页的情况下,也不执行内存分页
    return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
  }
}

executeAutoCount 和 pageQuery 差不多

是可以自定义 count 方法的

分页/count SQL

public abstract class AbstractHelperDialect extends AbstractDialect implements Constant {
  public abstract String getPageSql(String sql, Page page, CacheKey pageKey);
}

MySQL 的实现

public class MySqlDialect extends AbstractHelperDialect {
  @Override
  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("\n LIMIT ? ");
    } else {
        sqlBuilder.append("\n LIMIT ?, ? ");
    }
    return sqlBuilder.toString();
  }
}

分页需要参数,参数处理
pageQuery 中调用了处理参数对象

public abstract class AbstractHelperDialect extends AbstractDialect implements Constant {
    @Override
    public Object processParameterObject(MappedStatement ms, Object parameterObject, BoundSql boundSql, CacheKey pageKey) {
        //处理参数
        Page page = getLocalPage();
        //如果只是 order by 就不必处理参数
        if (page.isOrderByOnly()) {
            return parameterObject;
        }
        Map<String, Object> paramMap = null;
        if (parameterObject == null) {
            paramMap = new HashMap<String, Object>();
        } else if (parameterObject instanceof Map) {
            //解决不可变Map的情况
            paramMap = new HashMap<String, Object>();
            paramMap.putAll((Map) parameterObject);
        } else {
            paramMap = new HashMap<String, Object>();
            // sqlSource为ProviderSqlSource时,处理只有1个参数的情况
            if (ms.getSqlSource() instanceof ProviderSqlSource) {
                String[] providerMethodArgumentNames = ExecutorUtil.getProviderMethodArgumentNames((ProviderSqlSource) ms.getSqlSource());
                if (providerMethodArgumentNames != null && providerMethodArgumentNames.length == 1) {
                    paramMap.put(providerMethodArgumentNames[0], parameterObject);
                    paramMap.put("param1", parameterObject);
                }
            }
            //动态sql时的判断条件不会出现在ParameterMapping中,但是必须有,所以这里需要收集所有的getter属性
            //TypeHandlerRegistry可以直接处理的会作为一个直接使用的对象进行处理
            boolean hasTypeHandler = ms.getConfiguration().getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());
            MetaObject metaObject = MetaObjectUtil.forObject(parameterObject);
            //需要针对注解形式的MyProviderSqlSource保存原值
            if (!hasTypeHandler) {
                for (String name : metaObject.getGetterNames()) {
                    paramMap.put(name, metaObject.getValue(name));
                }
            }
            //下面这段方法,主要解决一个常见类型的参数时的问题
            if (boundSql.getParameterMappings() != null && boundSql.getParameterMappings().size() > 0) {
                for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
                    String name = parameterMapping.getProperty();
                    if (!name.equals(PAGEPARAMETER_FIRST)
                            && !name.equals(PAGEPARAMETER_SECOND)
                            && paramMap.get(name) == null) {
                        if (hasTypeHandler
                                || parameterMapping.getJavaType().equals(parameterObject.getClass())) {
                            paramMap.put(name, parameterObject);
                            break;
                        }
                    }
                }
            }
        }
        return processPageParameter(ms, paramMap, page, boundSql, pageKey);
    }
}