mybatis第七话 - mybatis插件Interceptor之pagehelper的源码分析

884 阅读10分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

本篇内容主要记录mybatis插件篇之pagehelper的源码分析过程。

上篇文章已经描述pagehelper的分页功能如何使用了,那原理肯定得探索一下了。

jar包依赖

基于springboot 2.5.6 只贴和源码相关的了。

<!--mybatis-->
<dependency>
 <groupId>org.mybatis.spring.boot</groupId>
 <artifactId>mybatis-spring-boot-starter</artifactId>
 <version>2.2.0</version>
</dependency>
<!--分页-->
<dependency>
 <groupId>com.github.pagehelper</groupId>
 <artifactId>pagehelper-spring-boot-starter</artifactId>
 <version>1.4.0</version>
</dependency>

1. startPage和clearPage

1.1 PageHelper.startPage(1, 10);

/**
* 开始分页
 *
 * @param pageNum      页码
 * @param pageSize     每页显示数量
 * @param count        是否进行count查询
 * @param reasonable   分页合理化,null时用默认配置
 * @param pageSizeZero true且pageSize=0时返回全部结果,false时分页,null时用默认配置
 */
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);
    //合理化配置需要设置reasonable=true 页码<=0是转变为1
    page.setReasonable(reasonable);
    page.setPageSizeZero(pageSizeZero);
    //当已经执行过orderBy的时候
    Page<E> oldPage = getLocalPage();
    if (oldPage != null && oldPage.isOrderByOnly()) {
        page.setOrderBy(oldPage.getOrderBy());
    }
    //保存到本地线程变量
    setLocalPage(page);
    return page;
}
  • 核心的顶层存储
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();

//Page
public class Page<E> extends ArrayList<E> implements Closeable {
    private static final long serialVersionUID = 1L;

    /**
     * 页码,从1开始
     */
    private int pageNum;
    /**
     * 页面大小
     */
    private int pageSize;
    /**
     * 起始行
     */
    private long startRow;
    /**
     * 末行
     */
    private long endRow;
    /**
     * 总数
     */
    private long total;
    /**
     * 总页数
     */
    private int pages;
    /**
     * 包含count查询
     */
    private boolean count = true;
    /**
     * 分页合理化
     */
    private Boolean reasonable;
    /**
     * 当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果
     */
    private           Boolean                   pageSizeZero;
    /**
     * 进行count查询的列名
     */
    private           String                    countColumn;
    /**
     * 排序
     */
    private           String                    orderBy;
    /**
     * 只增加排序
     */
    private           boolean                   orderByOnly;
    /**
     * sql拦截处理
     */
    private           BoundSqlInterceptor       boundSqlInterceptor;
    private transient BoundSqlInterceptor.Chain chain;
    /**
     * 分页实现类,可以使用 {@link com.github.pagehelper.page.PageAutoDialect} 类中注册的别名,例如 "mysql", "oracle"
     */
    private           String                    dialectClass;
}

1.2 PageHelper.clearPage();

//移除本地变量
public static void clearPage() {
    LOCAL_PAGE.remove();
}

2.pagehelper的代码织入过程

2.1 由spring管理的interceptor bean初始化过程

回到mybatis源码的自动装配类MybatisAutoConfiguration

public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider,
    ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider,
    ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,
    ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
  this.properties = properties;
  //根据类型取容器中找所有Interceptor接口的实现类 ### 大概看下
  this.interceptors = interceptorsProvider.getIfAvailable();
  //根据类型取容器中找所有TypeHandler接口的实现类
  this.typeHandlers = typeHandlersProvider.getIfAvailable();
  this.languageDrivers = languageDriversProvider.getIfAvailable();
  this.resourceLoader = resourceLoader;
  this.databaseIdProvider = databaseIdProvider.getIfAvailable();
  this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
}
  • DefaultListableBeanFactory -> doResolveDependency
//获取泛型的class类型
Class<?> type = descriptor.getDependencyType();

//根据多个注入条件查询 ### 
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {
 return multipleBeans;
}

//DefaultListableBeanFactory#resolveMultipleBeans
//符合注入的候选bean会被通过getBean获取实例
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType,
     new MultiElementDescriptor(descriptor));
  • 在初始化bean sqlSessionFactory
if (!ObjectUtils.isEmpty(this.interceptors)) {
  factory.setPlugins(this.interceptors);
}

//SqlSessionFactoryBean#buildSqlSessionFactory
if (!isEmpty(this.plugins)) {
  Stream.of(this.plugins).forEach(plugin -> {
    //最终添加到InterceptorChain类
    targetConfiguration.addInterceptor(plugin);
    LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
  });
}

这里指的注意的是,这里找到的interceptor实现是已经向容器中注册了的,也就是已经交给spring管理的interceptor bean

2.2 pagehelper的初始化过程

由于断点发现pagehelper并没有交由spring管理,那么它的入口只有以下几种情况

  • 由开发者在代码里面手动注入,例如@Bean
  • springboot项目中的自动装配类EnableAutoConfiguration

于是在pagehelper-spring-boot-autoconfigure-1.4.0.jar/META-INF/spring.factories找到如下内容

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.github.pagehelper.autoconfigure.PageHelperAutoConfiguration
  • pagehelper 入口PageHelperAutoConfiguration
//该类实现了InitializingBean 于是直接找到这个回调
@Override
public void afterPropertiesSet() throws Exception {
 //new一个Interceptor对象
     PageInterceptor interceptor = new PageInterceptor();
     Properties properties = new Properties();
     //先把一般方式配置的属性放进去
     properties.putAll(pageHelperProperties());
     //在把特殊配置放进去,由于close-conn 利用上面方式时,属性名就是 close-conn 而不是 closeConn,所以需要额外的一步
     properties.putAll(this.properties.getProperties());
     interceptor.setProperties(properties);
     for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
         org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration();
         if (!containsInterceptor(configuration, interceptor)) {
             //通过mybatis中的Configuration添加到InterceptorChain里面
             configuration.addInterceptor(interceptor);
         }
     }
 }
  • InterceptorChain#addInterceptor
public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<>();
  //调用阶段封装 后面分析
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  //添加到本地变量中
  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }
}

到这里所有的interceptor实现就都已经保存到InterceptorChain中的list里面了,初始化阶段完成。

2.3 pagehelper的织入阶段

回顾一下mybatis的调用流程,从我们自己手写的xxxMapper接口开始

MapperProxy#invoke -> 内部类PlainMethodInvoker#invoke -> MapperMethod#execute -> SqlSessionTemplate#selectList-> 内部代理类SqlSessionInterceptor#invoke(创建新的sqlseesion,事务的自动提交在这里控制,执行器插件织入均在这里初始化) ->
DefaultSqlSession#selectList -> BaseExecutor#query(此处executor可被插件代理,甚至多层) -> 到对应的执行器例如SimpleExecutor#doQuery(预处理参数prepareStatement-> PreparedStatementHandler#parameterize中的parameterHandler可被多层代理) -> SimpleStatementHandler#query(随执行器而定,可被多层代理) -> DefaultResultSetHandler#handleResultSets(ResultSetHandler可被代理) -> 返回list

可被代理的 Executor,ParameterHandler,StatementHandler,ResultSetHandler均可被插件代理,本篇文章重点分析的是Executor

  • 直接找到SqlSessionTemplate内部类SqlSessionInterceptor#invoke 创建sqlSeesion时会创建执行器的代码
//getSqlSession -> DefaultSqlSessionFactory#openSession -> openSessionFromDataSource
final Executor executor = configuration.newExecutor(tx, execType);

//根据不同的类型创建执行器
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  executorType = executorType == null ? defaultExecutorType : executorType;
  executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  Executor executor;
  if (ExecutorType.BATCH == executorType) {
    executor = new BatchExecutor(this, transaction);
  } else if (ExecutorType.REUSE == executorType) {
    executor = new ReuseExecutor(this, transaction);
  } else {
    executor = new SimpleExecutor(this, transaction);
  }
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
  //插件的织入 ##
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}
  • 再次来到InterceptorChain
public Object pluginAll(Object target) {
  for (Interceptor interceptor : interceptors) {
    target = interceptor.plugin(target);
  }
  return target;
}

//Interceptor接口有默认实现类
default Object plugin(Object target) {
  return Plugin.wrap(target, this);
}
  • Plugin#wrap
public static Object wrap(Object target, Interceptor interceptor) {
//解析
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
 Class<?> type = target.getClass();
 //需要该类有接口才代理
 Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
 if (interfaces.length > 0) {
   //生成代理对象 invoke对象是Plugin
   return Proxy.newProxyInstance(
       type.getClassLoader(),
       interfaces,
       new Plugin(target, interceptor, signatureMap));
 }
 return target;
}
  • Plugin#getSignatureMap解析过程
//对应这个注解内容
@Intercepts(value = {@Signature(type = Executor.class, method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
 //获取Intercepts注解
  Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
  // issue #251
  if (interceptsAnnotation == null) {
    throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
  }
  //取出value 下面是for循环 也就是说一个实现了可以代理多个类和方法
  Signature[] sigs = interceptsAnnotation.value();
  Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
  for (Signature sig : sigs) {
    //不存在才走里面的逻辑
    Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>());
    try {
      //获取方法
      Method method = sig.type().getMethod(sig.method(), sig.args());
      methods.add(method);
    } catch (NoSuchMethodException e) {
      throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
    }
  }
  return signatureMap;
}

从上述内容可以看到,Executor创建时会经过插件链路pluginAll方法生成代理类,invoke类对象是Plugin,那直接来到invoke类。

2.4 pagehelper的调用阶段

  • 找到这里BaseExecutor#query在有插件的实现下,被代理的情况下应该会到Plugin的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    Set<Method> methods = signatureMap.get(method.getDeclaringClass());
    //方法类是否被代理
    if (methods != null && methods.contains(method)) {
      //是就走这个 那直接来到初始化添加进来的PageInterceptor类
      return interceptor.intercept(new Invocation(target, method, args));
    }
    return method.invoke(target, args);
  } catch (Exception e) {
    throw ExceptionUtil.unwrapThrowable(e);
  }
}
  • PageInterceptor#intercept
@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();
        //对 boundSql 的拦截处理
        if (dialect instanceof BoundSqlInterceptor.Chain) {
            boundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.ORIGINAL, boundSql, cacheKey);
        }
        List resultList;
        //调用方法判断是否需要进行分页,如果不需要,直接返回结果
        if (!dialect.skip(ms, parameter, rowBounds)) {
            //判断是否需要进行 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);
                }
            }
            //原始sql查询 
            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();
        }
    }
}

2.5 PageInterceptor#count count查询阶段

private Long count(Executor executor, MappedStatement ms, Object parameter,
                   RowBounds rowBounds, ResultHandler resultHandler,
                   BoundSql boundSql) throws SQLException {
    //countSuffix=_COUNT  statementId变更              
    String countMsId = ms.getId() + countSuffix;
    Long count;
    //先判断是否存在手写的 count 查询 就是同一个statementId
    MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);
    if (countMs != null) {
        //存在就直接执行
        count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
    } else {
     //pagehelper count statement本地存储map
        if (msCountMap != null) {
            countMs = msCountMap.get(countMsId);
        }
        //自动创建
        if (countMs == null) {
            //根据当前的 ms 创建一个返回值为 Long 类型的 MappedStatement
            countMs = MSUtils.newCountMappedStatement(ms, countMsId);
            if (msCountMap != null) {
             //缓存到本地
                msCountMap.put(countMsId, countMs);
            }
        }
        //执行 ###
        count = ExecutorUtil.executeAutoCount(this.dialect, executor, countMs, parameter, boundSql, rowBounds, resultHandler);
    }
    return count;
}
  • ExecutorUtil#executeAutoCount
public static Long executeAutoCount(Dialect dialect, Executor executor, MappedStatement countMs,
                                    Object parameter, BoundSql boundSql,
                                    RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
    //创建 count 查询的缓存 key
    CacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);
    //调用方言获取 count sql  sql转变的过程 ###
    String countSql = dialect.getCountSql(countMs, boundSql, parameter, rowBounds, countKey);
    //countKey.update(countSql); 生成一个新的BoundSql
    BoundSql countBoundSql = new BoundSql(countMs.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
    //当使用动态 SQL 时,可能会产生临时的参数,这些参数需要手动设置到新的 BoundSql 中
    for (String key : additionalParameters.keySet()) {
        countBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
    }
    //对 boundSql 的拦截处理
    if (dialect instanceof BoundSqlInterceptor.Chain) {
        countBoundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.COUNT_SQL, countBoundSql, countKey);
    }
    //执行 count 查询
    Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
    //某些数据(如 TDEngine)查询 count 无结果时返回 null
    if (countResultList == null || ((List) countResultList).isEmpty()) {
        return 0L;
    }
    return ((Number) ((List) countResultList).get(0)).longValue();
}
  • AbstractHelperDialect#getCountSql
@Override
public String getCountSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey countKey) {
 //获取本地线程变量
    Page<Object> page = getLocalPage();
    //可以自定义count指定列 例如:count(uid)
    String countColumn = page.getCountColumn();
    if (StringUtil.isNotEmpty(countColumn)) {
        return countSqlParser.getSmartCountSql(boundSql.getSql(), countColumn);
    }
    //没设置 就走这里 ###
    return countSqlParser.getSmartCountSql(boundSql.getSql());
}
  • CountSqlParser#getSmartCountSql
public String getSmartCountSql(String sql) {
    return getSmartCountSql(sql, "0");
}

/**
 * 获取智能的countSql
 *
 * @param sql
 * @param countColumn 列名,默认 0
 * @return
 */
public String getSmartCountSql(String sql, String countColumn) {
    //解析SQL
    Statement stmt = null;
    //特殊sql不需要去掉order by时,使用注释前缀
    if(sql.indexOf(KEEP_ORDERBY) >= 0){
        return getSimpleCountSql(sql, countColumn);
    }
    try {
     //sql解析 会得到查询的字段 存储在withItemsList里面
        stmt = CCJSqlParserUtil.parse(sql);
    } catch (Throwable e) {
        //无法解析的用一般方法返回count语句 ###
        return getSimpleCountSql(sql, countColumn);
    }
    Select select = (Select) stmt;
    SelectBody selectBody = select.getSelectBody();
    try {
        //处理body-去order by
        processSelectBody(selectBody);
    } catch (Exception e) {
        //当 sql 包含 group by 时,不去除 order by
        return getSimpleCountSql(sql, countColumn);
    }
    //处理with-去order by
    processWithItemsList(select.getWithItemsList());
    //处理为count查询 ###
    sqlToCount(select, countColumn);
    String result = select.toString();
    return result;
}
  • 一般的count语句getSimpleCountSql
public String getSimpleCountSql(final String sql, String name) {
    StringBuilder stringBuilder = new StringBuilder(sql.length() + 40);
    stringBuilder.append("select count(");
    stringBuilder.append(name);
    stringBuilder.append(") from ( \n");
    stringBuilder.append(sql);
    stringBuilder.append("\n ) tmp_count");
    return stringBuilder.toString();
}
  • 替换查询列的count语句sqlToCount
//只贴这几行了
// 是否能简化count查询
List<SelectItem> COUNT_ITEM = new ArrayList<SelectItem>();
//name 默认“0” 或者指定的字段
COUNT_ITEM.add(new SelectExpressionItem(new Column("count(" + name +")")));
if (selectBody instanceof PlainSelect && isSimpleCount((PlainSelect) selectBody)) {
 //把解析出来的查询列字段替换成 count(0)了
    ((PlainSelect) selectBody).setSelectItems(COUNT_ITEM);
}
  • 到这里第一步完成了count的查询了,贴下count后续的操作
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.getPageSizeZero() != null) {
        //PageSizeZero=false&&pageSize<=0
        if (!page.getPageSizeZero() && page.getPageSize() <= 0) {
            return false;
        }
        //PageSizeZero=true&&pageSize<0 返回 false,只有>=0才需要执行后续的
        else if (page.getPageSizeZero() && page.getPageSize() < 0) {
            return false;
        }
    }
    //页码>0 && 开始行数<总行数即可,不需要考虑 pageSize(上面的 if 已经处理不符合要求的值了)
    return page.getPageNum() > 0 && count > page.getStartRow();
}

2.6 pageQuery原始sql查询阶段

  • 再次回调PageInterceptor#intercept找到可以list查询pageQuery
//ExecutorUtil#pageQuery
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);
    }
}
  • AbstractHelperDialect#getPageSql
 @Override
 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);
     }
     //如果只支持order by 就不用处理limit了
     if (page.isOrderByOnly()) {
         return sql;
     }
     //该类实现了Dialect 这里是委派模式了 到各种数据库方言类
     //这里是mysql 
     return getPageSql(sql, page, pageKey);
 }
  • MySqlDialect类,共两个方法
//处理参数
@Override
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());
        //如果开头是0 可以省略第一个参数 名称和参数里面的保持一致
        if (page.getStartRow() == 0) {
            newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_SECOND, int.class).build());
        } else {
            newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_FIRST, long.class).build());
            newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_SECOND, int.class).build());
        }
        MetaObject metaObject = MetaObjectUtil.forObject(boundSql);
        metaObject.setValue("parameterMappings", newParameterMappings);
    }
    return paramMap;
}

@Override
public String getPageSql(String sql, Page page, CacheKey pageKey) {
    StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
    sqlBuilder.append(sql);
    //这里就是动态添加limit了
    if (page.getStartRow() == 0) {
        sqlBuilder.append("\n LIMIT ? ");
    } else {
        sqlBuilder.append("\n LIMIT ?, ? ");
    }
    return sqlBuilder.toString();
}

这里limit的sql已经添加完了,后面就是直接调用执行了,整个流程到这里就结束了。

3.总结

实现了mybatis插件的pagehelper,通过本地线程变量执行了count总数查询,然后动态修改了sql,达到分页的一个效果。

这里记录一下值得注意的几个点:

  • page==0 || pageSize==0只会执行count查询,分页查询不会查询
//Page 中计算起止行号
private void calculateStartAndEndRow() {
 //pageNum=0 那么 startRow=0
    this.startRow = this.pageNum > 0 ? (this.pageNum - 1) * this.pageSize : 0;
    //0 + pageSize * 0 = 0
    this.endRow = this.startRow + this.pageSize * (this.pageNum > 0 ? 1 : 0);
}

//在count查询完成后 AbstractHelperDialect#afterCount方法中
//pageSizeZero 默认false 就是pageSize==0 就不继续查分页
if (!page.getPageSizeZero() && page.getPageSize() <= 0) {
    return false;
}
//如果设置pageSize为0时也继续查询分页数据 那么要count结果需>=0才继续
else if (page.getPageSizeZero() && page.getPageSize() < 0) {
    return false;
}
  • PageInterceptor#intercept找到最后返回。
return dialect.afterPage(resultList, parameter, rowBounds);

这里是没有传递参数Invocation invocation过去的,也就是查询链的流程到这里就截止了

基于springboot的自动装配机制,先扫描启动类目录下的class类,再是自动装配类EnableAutoConfiguration配置下的类初始化,手写开发的查询插件先初始化PageInterceptor分页插件,在InterceptorChain#pluginAll最后封装的是PageInterceptor分页插件,然后分页最终执行的返回并不是invocation.proceed(),也就是手写的查询插件会失效掉。

以上就是本章的全部内容了。

上一篇:mybatis第六话 - mybatis插件篇之pagehelper的使用 下一篇:mybatis第八话 - mybaits之ParameterHandler参数处理源码分析

旧书不厌百回读,熟读精思子自知