MyBatis 源码分析篇 4:Mapper 方法执行

通过上一篇文章 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.selectList(command.getName(), param);。果然,到这一步就验证了我们之前的猜想,它果然是调用了 SqlSession 的 selectList 方法!

至此,代码执行就回到了 SqlSession 的 selectList,我们在之后的文章 MyBatis 源码分析篇 5:Mapper 方法执行之 Executor 详细讨论其底层实现。

附:

当前版本:mybatis-3.5.0
官网文档:MyBatis
项目实践:MyBatis Learn
手写源码:MyBatis 简易实现

你可能感兴趣的:(MyBatis 源码分析篇 4:Mapper 方法执行)