通过上一篇文章 MyBatis 源码分析篇 3:getMapper 我们已经知道 MyBatis 通过动态代理的方式获取 Mapper 实例。在这一篇文章中我们就来具体讨论其动态代理的具体实现和 Mapper 中方法是如何执行的。
以下面代码为入口,我们来进行 debug 代码跟入:
List 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 mapperInterface;
private final Map methodCache;
public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
这也完全在我们意料之中,不知道大家还记不记得我们在 MyBatis 源码分析篇 2:SqlSession 中讨论的,SqlSession 包含了执行数据库操作的所有方法。那么我们反过来想一下,MapperProxy 代理类是要对我们在 Mapper 中声明的方法进行执行,而数据库操作的方法都在 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;
}
在上面的代码中,因为我们测试执行的是返回值为 List 类型的 select 方法,那么接着会进入第 10 行代码:result = executeForMany(sqlSession, args); 其实现为:
private Object executeForMany(SqlSession sqlSession, Object[] args) {
List result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.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,那么会执行第 8 行代码:result = sqlSession.
至此,代码执行就回到了 SqlSession 的 selectList,我们在之后的文章 MyBatis 源码分析篇 5:Mapper 方法执行之 Executor 详细讨论其底层实现。
附:
当前版本:mybatis-3.5.0
官网文档:MyBatis
项目实践:MyBatis Learn
手写源码:MyBatis 简易实现