美文网首页
MyBatis 源码分析篇 4:Mapper 方法执行

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

作者: 兆雪儿 | 来源:发表于2019-04-19 17:26 被阅读0次

    通过上一篇文章 MyBatis 源码分析篇 3:getMapper 我们已经知道 MyBatis 通过动态代理的方式获取 Mapper 实例。在这一篇文章中我们就来具体讨论其动态代理的具体实现和 Mapper 中方法是如何执行的。

    以下面代码为入口,我们来进行 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 包含了执行数据库操作的所有方法。那么我们反过来想一下,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 <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,那么会执行第 8 行代码:result = sqlSession.<E>selectList(command.getName(), param);。果然,到这一步就验证了我们之前的猜想,它果然是调用了 SqlSession 的 selectList 方法!

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

    附:

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

    相关文章

      网友评论

          本文标题:MyBatis 源码分析篇 4:Mapper 方法执行

          本文链接:https://www.haomeiwen.com/subject/sblcgqtx.html