通過上一篇文章 MyBatis 源碼分析篇 3:getMapper 我們已經(jīng)知道 MyBatis 通過動(dòng)態(tài)代理的方式獲取 Mapper 實(shí)例。在這一篇文章中我們就來具體討論其動(dòng)態(tài)代理的具體實(shí)現(xiàn)和 Mapper 中方法是如何執(zhí)行的。
以下面代碼為入口,我們來進(jìn)行 debug 代碼跟入:
List<Author> author = mapper.selectAllTest();
selectAllTest 方法的 sql 如下:
select id, name, sex, phone from author
不出意外的我們會(huì)首先進(jìn)入代理類 org.apache.ibatis.binding.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 if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
首先我們需要注意一下該類,該類持有一個(gè) SqlSession 類型的成員變量,并在構(gòu)造方法中進(jìn)行了賦值:
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
這也完全在我們意料之中,不知道大家還記不記得我們在 MyBatis 源碼分析篇 2:SqlSession 中討論的,SqlSession 包含了執(zhí)行數(shù)據(jù)庫操作的所有方法。那么我們反過來想一下,MapperProxy 代理類是要對我們在 Mapper 中聲明的方法進(jìn)行執(zhí)行,而數(shù)據(jù)庫操作的方法都在 SqlSession 中,那么它就一定要持有一個(gè) SqlSession,來調(diào)用 SqlSession 中對應(yīng)的方法!
接下來我們就來驗(yàn)證一下我們的猜想。我們再次回到 MapperProxy 的 invoke 方法。注意最后一行代碼:return mapperMethod.execute(sqlSession, args);,跟入該行代碼,會(huì)進(jìn)入 org.apache.ibatis.binding.MapperMethod 類的 execute(SqlSession sqlSession, Object[] args) 方法:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
//其余類型略...
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);
}
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;
}
在上面的代碼中,因?yàn)槲覀儨y試執(zhí)行的是返回值為 List 類型的 select 方法,那么接著會(huì)進(jìn)入第 10 行代碼:result = executeForMany(sqlSession, args); 其實(shí)現(xiàn)為:
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
因?yàn)槲覀儾]有綁定 RowBounds,那么會(huì)執(zhí)行第 8 行代碼:result = sqlSession.<E>selectList(command.getName(), param);。果然,到這一步就驗(yàn)證了我們之前的猜想,它果然是調(diào)用了 SqlSession 的 selectList 方法!
至此,代碼執(zhí)行就回到了 SqlSession 的 selectList,我們在之后的文章 MyBatis 源碼分析篇 5:Mapper 方法執(zhí)行之 Executor 詳細(xì)討論其底層實(shí)現(xiàn)。
附:
當(dāng)前版本:mybatis-3.5.0
官網(wǎng)文檔:MyBatis
項(xiàng)目實(shí)踐:MyBatis Learn
手寫源碼:MyBatis 簡易實(shí)現(xiàn)