上文已經(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 的具體使用,下一篇等你。