通過上一篇文章 MyBatis 源碼分析篇 3:getMapper 我們已經知道 MyBatis 通過動態(tài)代理的方式獲取 Mapper 實例灯萍。在這一篇文章中我們就來具體討論其動態(tài)代理的具體實現(xiàn)和 Mapper 中方法是如何執(zhí)行的。
以下面代碼為入口齿风,我們來進行 debug 代碼跟入:
List<Author> author = mapper.selectAllTest();
selectAllTest 方法的 sql 如下:
select id, name, sex, phone from author
不出意外的我們會首先進入代理類 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);
}
首先我們需要注意一下該類救斑,該類持有一個 SqlSession 類型的成員變量诊笤,并在構造方法中進行了賦值:
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ù)據庫操作的所有方法晾匠。那么我們反過來想一下梯刚,MapperProxy 代理類是要對我們在 Mapper 中聲明的方法進行執(zhí)行,而數(shù)據庫操作的方法都在 SqlSession 中澜共,那么它就一定要持有一個 SqlSession锥腻,來調用 SqlSession 中對應的方法!
接下來我們就來驗證一下我們的猜想京革。我們再次回到 MapperProxy 的 invoke 方法。注意最后一行代碼:return mapperMethod.execute(sqlSession, args);匹摇,跟入該行代碼廊勃,會進入 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;
}
在上面的代碼中坡垫,因為我們測試執(zhí)行的是返回值為 List 類型的 select 方法,那么接著會進入第 10 行代碼:result = executeForMany(sqlSession, args); 其實現(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;
}
因為我們并沒有綁定 RowBounds胎源,那么會執(zhí)行第 8 行代碼:result = sqlSession.<E>selectList(command.getName(), param);屿脐。果然的诵,到這一步就驗證了我們之前的猜想,它果然是調用了 SqlSession 的 selectList 方法西疤!
至此,代碼執(zhí)行就回到了 SqlSession 的 selectList扰她,我們在之后的文章 MyBatis 源碼分析篇 5:Mapper 方法執(zhí)行之 Executor 詳細討論其底層實現(xiàn)芭碍。
附:
當前版本:mybatis-3.5.0
官網文檔:MyBatis
項目實踐:MyBatis Learn
手寫源碼:MyBatis 簡易實現(xiàn)