天天看点

Spring整合Mybatis源码剖析(五)-dao接口执行调用方法怎么找到对应的xml执行sql

学习源码过程中随手记录的笔记,仅供参考,有问题欢迎指出交流

可能比较枯燥,耐点心,但是弄懂了,必能知其然而知其所以然

学习源码建议亲手debug调试

使用的源码版本

​	mybatis版本3.5.3

​	spring版本5.2.0
           

前面分析完dao接口会被创建动态代理加入到spring容器中,可以供我们使用

现在看下当执行dao接口里的方法时,是如何找到对应的xml并执行sql的

当调用mapper接口中的方法时,由于这个接口已经被动态代理加强过,那么会走到MapperProxy的invoke方法

MapperProxy#invoke()

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 (method.isDefault()) {   
       
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    /**
     * 真正的进行调用,做了二个事情
     * 第一步:把我们的方法对象封装成一个MapperMethod对象(带有缓存作用的)
     */
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    
    //执行目标方法
    return mapperMethod.execute(sqlSession, args);
  }
           

进入

cachedMapperMethod(method);

private MapperMethod cachedMapperMethod(Method method) {
    /**
     * 相当于这句代码.jdk8的新写法
     * if(methodCache.get(method)==null){
     *     methodCache.put(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()))
     * }
     */
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }
           

进入

new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    /**
     * 创建我们的SqlCommand对象
     */
    this.command = new SqlCommand(config, mapperInterface, method);
    /**
     * 创建我们的方法签名对象
     */
    this.method = new MethodSignature(config, mapperInterface, method);
  }
           

进入

new SqlCommand(config, mapperInterface, method);

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
  //获取我们的方法的名称 selectById
  final String methodName = method.getName();
  //方法所在接口的类型 UserMapper
  final Class<?> declaringClass = method.getDeclaringClass();
  /**
   * 根据接口,方法名称解析出我们对应的mapperStatment对象
   */
  MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
      configuration);
  if (ms == null) {
    if (method.getAnnotation(Flush.class) != null) {
      name = null;
      type = SqlCommandType.FLUSH;
    } else {
      throw new BindingException("Invalid bound statement (not found): "
          + mapperInterface.getName() + "." + methodName);
    }
  } else {
    //把我们的mappedStatmentID(com.cheng.mapper.UserMapper.selectById)
    name = ms.getId();
    //sql操作的类型(比如insert|delete|update|select)
    type = ms.getSqlCommandType();
    if (type == SqlCommandType.UNKNOWN) {
      throw new BindingException("Unknown execution method for: " + name);
    }
  }
}
           

进入

resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration)

还记得前面解析xml文章提到过两个缓存map,其中一个就是将sql封装成mappedStatement放入缓存中,这边获取之前解析的xml文件,在这去拿了

private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
        Class<?> declaringClass, Configuration configuration) {
      //获取我们的sql对应的statmentId(com.cheng.mapper.UserMapper + selectById)
      String statementId = mapperInterface.getName() + "." + methodName;
      //根据我们的statmentId判断我们的主配置类是否包含 了我们的mapperStatment对象
      if (configuration.hasStatement(statementId)) {
        //存在通过key获取对应的mapperStatment对象返回
        return configuration.getMappedStatement(statementId);
      } else if (mapperInterface.equals(declaringClass)) {
        return null;
      }
      /**
       * 获取我们mapper接口的父类接口
       */
      for (Class<?> superInterface : mapperInterface.getInterfaces()) {
        //判断方法所在的类是否实现了superInterface
        if (declaringClass.isAssignableFrom(superInterface)) {
          //解析我们父类的MappedStatment对象
          MappedStatement ms = resolveMappedStatement(superInterface, methodName,
              declaringClass, configuration);
          if (ms != null) {
            return ms;
          }
        }
      }
      return null;
    }
  }
           

根据接口里的statementId (接口全限定名+方法名 - 对应xml的nameSpace + selectid)

去mybatis全局配置configuration中找对应的mappedStatements

这里就是通过接口dao的调用最终怎么找到对应的xml的

Spring整合Mybatis源码剖析(五)-dao接口执行调用方法怎么找到对应的xml执行sql

最终会封装成MapperMethod对象,里面包含调用方法名称,执行的语句,返回值,参数值…

Spring整合Mybatis源码剖析(五)-dao接口执行调用方法怎么找到对应的xml执行sql

通过dao方法的调用封装了对应的xml执行语句,下面再看看如何执行的

进入

MapperProxy#invoke# mapperMethod.execute(sqlSession, args);

根据MapperMehod获取对应的操作类型通过SqlSessionTemplate调用即可

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    /**
     * 判断我们执行sql命令的类型
     */
    switch (command.getType()) {
      //insert操作
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      //update操作
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      //delete操作
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      //select操作
      case SELECT:
        //返回值为空
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          //返回值是一个List
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          //返回值是一个map
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          //返回游标
          result = executeForCursor(sqlSession, args);
        } else {
          //查询返回单个

          /**
           * 解析我们的参数
           */
          Object param = method.convertArgsToSqlCommandParam(args);
          /**
           * 通过调用sqlSessionTemplate来执行我们的sql
           * 第一步:获取我们的statmentName(com.cheng.mapper.UserMapper.selectById)
           * 然后我们就需要重点研究下SqlSessionTemplate是怎么来的?
           * 在mybatis和spring整合的时候,我们偷天换日了我们mapper接口包下的所有的
           * beandefinition改成了MapperFactoryBean类型的
           * MapperFactoryBean<T> extends SqlSessionDaoSupport的类实现了SqlSessionDaoSupport
           * 那么就会调用他的setXXX方法为我们的sqlSessionTemplate赋值
           *
           */
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        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;
  }
           

到这整个spring整合mybatis源码已经剖析完毕了

只要跟着源码多走几遍基本能弄懂一点吧,不得不说spring还是很强大,集成三方处理的很好

如果觉得还算凑合,帮我点个赞,谢谢