关于mybatis:透过PageHelper看Mybatis插件机制

一、startPage做了什么

PageHelper.startPage(1,20);

这是PageHelper举荐的应用形式,后续的第一个查询方法会分页。

察看这个办法的外部逻辑

public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count) {
    // 封装成page对象
    Page<E> page = new Page<E>(pageNum, pageSize, count);
    // == 将page对象存在`PageMethod#LOCAL_PAGE`——这是一个ThreadLocal
    setLocalPage(page);
    return page;
}

二、PageHelper如何与Mybatis建立联系的?

正是通过mybatis的插件机制plugin,入口在executor创立局部。

org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession()
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType){
    // == 过滤器链对executor做封装
    executor = (Executor) interceptorChain.pluginAll(executor);
}

察看InterceptorChain interceptorChain构造:

class InterceptorChain {
    // ## 1.拦截器
    private final List<Interceptor> interceptors = new ArrayList<>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      // ## 2.包装
      target = interceptor.plugin(target);
    }
    return target;
  }

1.拦截器

被@Intercepts注解润饰的类就是拦截器,也就是所谓的插件。

以PageInterceptor为例:

@Intercepts(
    {
        // 通过@Signature指定被拦挡的办法
        @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

2.包装

查看具体的包装办法

default Object plugin(Object target) {
    return Plugin.wrap(target, this);
            ⬇⬇⬇⬇⬇⬇
            // == 外围逻辑是动静代理
            return Proxy.newProxyInstance(
                      type.getClassLoader(),
                      interfaces,
                      new Plugin(target, interceptor, signatureMap));
}

察看invokcationHandler(Plugin)的invoke办法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 执行拦截器的拦挡办法
    return interceptor.intercept(new Invocation(target, method, args));
}

察看PageInterceptor#intercept实现:

com.github.pagehelper.PageInterceptor#intercept{
  // == 判断是否须要进行分页,如果不须要,间接返回后果
  if (!dialect.skip(ms, parameter, rowBounds)) {
      // -- 判断是否须要进行分页查问,page.getPageSize>0
      if (dialect.beforePage(ms, parameter, rowBounds)) {
          // -- 调用方言获取分页sql,增加limit语句
          String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
          //执行分页查问
          resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
       }
   }
}
  • 怎么判断是否须要分页?
com.github.pagehelper.PageHelper#skip
public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {  
    // == PageMethod#LOCAL_PAGE中是否有值
    Page page = pageParams.getPage(parameterObject, rowBounds);

    // -- PageMethod#LOCAL_PAGE有值状况,初始化方言
    autoDialect.initDelegateDialect(ms);
    return false;
}

这里就和第一节连上了。
PageHelper.startPage向ThreadLocal中寄存了page对象
此处skip()办法判断:如果有值返回false,需分页同时初始化方言dialect;无值返回true,不用分页。

  • 方言dialect的初始化逻辑
com.github.pagehelper.page.PageAutoDialect#initDelegateDialect{
    this.delegate = getDialect(ms);
}
com.github.pagehelper.page.PageAutoDialect#getDialect{
    // 通过数据库链接截取别名
    String dialectStr = fromJdbcUrl(url);
    // -- 通过别名初始化
    AbstractHelperDialect dialect = initDialect(dialectStr, properties);
}

com.github.pagehelper.page.PageAutoDialect#initDialect{
    // 解析方言的class
    Class sqlDialectClass = resloveDialectClass(dialectClass);
                                ⬇⬇⬇⬇⬇
                            // == 通过dialectAliasMap获取            
                            dialectAliasMap.get(className.toLowerCase());
    // 通过反射创立
    dialect = (AbstractHelperDialect) sqlDialectClass.newInstance();
}

咱们看看dialectAliasMap中存了什么

com.github.pagehelper.page.PageAutoDialect#dialectAliasMap
static {
    //初始化时注册别名
    dialectAliasMap.put("hsqldb", HsqldbDialect.class);
    dialectAliasMap.put("h2", HsqldbDialect.class);
    dialectAliasMap.put("postgresql", HsqldbDialect.class);
    dialectAliasMap.put("mysql", MySqlDialect.class);
}

mybatis预留扩大

// 参数解决
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
}
// 参数后果
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
}
// Statement解决
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
}

上述办法都有interceptorChain.pluginAll的影子,咱们能够本人写拦截器对ParameterHandler、ResultSetHandler、StatementHandler的要害办法进行拦挡,官网有具体介绍。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理