Mybatis隨筆(六) MapperProxy & Executor

上文已經(jīng)知道了Mybatis 通過JDK動態(tài)代理獲取到包含SQL方法的實體接口的代理對象 MapperProxy,接下來繼續(xù)看下SQL方法如何執(zhí)行。


1、MapperProxy#invoke流程

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else {
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
}
  • Object 方法直接就調(diào)用返回了,而非 Object 方法才走代理

最典型的兩個Object方法

  • hashCode
  • toString

我們的SQL方法明顯屬于非 Object 方法

private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
        return methodCache.computeIfAbsent(method, m -> {
            if (m.isDefault()) {
                try {
                    if (privateLookupInMethod == null) {
                        return new DefaultMethodInvoker(getMethodHandleJava8(method));
                    } else {
                        return new DefaultMethodInvoker(getMethodHandleJava9(method));
                    }
                } catch (IllegalAccessException | InstantiationException | InvocationTargetException
                    | NoSuchMethodException e) {
                    throw new RuntimeException(e);
                }
            } else {
                // 非接口默認實現(xiàn)方法代理
                return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
            }
        });
    } catch (RuntimeException re) {
        Throwable cause = re.getCause();
        throw cause == null ? re : cause;
    }
}

這里使用了一個緩存 methodCache

這個緩存是一個 Map<Method, MapperMethodInvoker> 類型,是一個方法到方法調(diào)用處理類的映射,保證同一個接口的同一個方法第二次調(diào)用時可以直接從緩存中獲取到調(diào)用對象,減少調(diào)用時長。

現(xiàn)在接口可以使用 default 關(guān)鍵字來擁有實現(xiàn)方法,而我們的SQL執(zhí)行方法是沒有實現(xiàn)的,所以走的是 PlainMethodInvoker 類的 invoke 方法
注意這里傳入了一個 MapperMethod 作為 PlainMethodInvoker 的構(gòu)造入?yún)?/p>

return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
}

入?yún)⑷齻€

  • 接口類 Class<?>
  • 方法類 Method
  • 配置類 Configuration

通過上面三個參數(shù)構(gòu)造了MapperMethod的兩個屬性

  • SqlCommand 內(nèi)部類,封裝了SQL ID 與 SQL 類型
  • MethodSignature 內(nèi)部類,保存了 Mapper 方法的信息
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    return mapperMethod.execute(sqlSession, args);
}

調(diào)用的是 MapperMethod 的 execute 方法,繼續(xù)往下跟

public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
  case INSERT: {
    Object param = method.convertArgsToSqlCommandParam(args);
    result = rowCountResult(sqlSession.insert(command.getName(), param));
    break;
  }
  case UPDATE: {
    Object param = method.convertArgsToSqlCommandParam(args);
    result = rowCountResult(sqlSession.update(command.getName(), param));
    break;
  }
  case DELETE: {
    Object param = method.convertArgsToSqlCommandParam(args);
    result = rowCountResult(sqlSession.delete(command.getName(), param));
    break;
  }
  case SELECT:
    if (method.returnsVoid() && method.hasResultHandler()) {
      executeWithResultHandler(sqlSession, args);
      result = null;
    } else if (method.returnsMany()) {
      result = executeForMany(sqlSession, args);
    } else if (method.returnsMap()) {
      result = executeForMap(sqlSession, args);
    } else if (method.returnsCursor()) {
      result = executeForCursor(sqlSession, args);
    } else {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = sqlSession.selectOne(command.getName(), param);
      if (method.returnsOptional()
          && (result == null || !method.getReturnType().equals(result.getClass()))) {
        result = Optional.ofNullable(result);
      }
    }
    break;
  case FLUSH:
    result = sqlSession.flushStatements();
    break;
  default:
    throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
  throw new BindingException("Mapper method '" + command.getName()
      + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}

我們發(fā)現(xiàn) MapperMethod就是一個類,上無老下無小,而這個 execute 方法就是對 SqlSession 的一個封裝(增刪改查),我們隨便找一個方法跟進去看看

Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
    && (result == null || !method.getReturnType().equals(result.getClass()))) {
    result = Optional.ofNullable(result);
}

sqlSession # selectOne 跟到最后執(zhí)行

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        MappedStatement ms = configuration.getMappedStatement(statement);
        return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

發(fā)現(xiàn)最后還是調(diào)用 executor 來執(zhí)行具體的處理邏輯.

executor 什么時候初始化的?怎么初始化的?

回過頭來找下 executor 的構(gòu)建,發(fā)現(xiàn)只在 DefaultSqlSession 構(gòu)造函數(shù)中進行賦值,而構(gòu)造函數(shù)只有兩處調(diào)用均在 DefaultSqlSessionFactory 中

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        final Environment environment = configuration.getEnvironment();
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 此處生成實際執(zhí)行對象 Executor
        final Executor executor = configuration.newExecutor(tx, execType);
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        closeTransaction(tx); // may have fetched a connection so lets call close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

具體看下 configuration.newExecutor(tx, execType) 的實現(xiàn)

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

這里前兩行我就有一個疑問:
執(zhí)行完第一行的 executorType,無論如何都不會是個 null,那么第二行的三目表達式肯定是取其自身,第二行的意義何在?

然后根據(jù) executorType 來創(chuàng)建對應(yīng)類型的 Executor

public enum ExecutorType {
    SIMPLE,REUSE,BATCH
}

SIMPLE 該類型的執(zhí)行器沒有特別的行為。它為每個語句的執(zhí)行創(chuàng)建一個新的預處理語句。
REUSE 該類型的執(zhí)行器會復用預處理語句。
BATCH 該類型的執(zhí)行器會批量執(zhí)行所有更新語句,如果 SELECT 在多個更新中間執(zhí)行,將在必要時將多條更新語句分隔開來,以方便理解。

這里面還藏著一個設(shè)計模式 - 裝飾器模式

if (cacheEnabled) {
  executor = new CachingExecutor(executor);
}

看下構(gòu)造函數(shù)和隨便一個查詢

private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager();

public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
}

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

CachingExecutor 持有了一個 Executor,從上面的邏輯來看,這個 Executor 是 BatchExecutor 、ReuseExecutor、SimpleExecutor 之間的一個,然后在實際調(diào)用時先走一遍緩存再去執(zhí)行持有的 Executor 中的方法,就像是在外部套了一層“衣服”,是為裝飾器模式。

CachingExecutor 就是 Mybatis 大名鼎鼎的二級緩存


Executor 的具體使用,下一篇等你。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容