【Mybatis源码分析】05-SqlSession执行过程之获取Mapper代理对象

当我们通过DefaultSqlSession的Mapper方式操作数据库时使用如下api:

 T getMapper(Class type);

此方法返回一个实现了type接口的实现类的实力,我们分析一下此实力的创建过程。

public  T getMapper(Class type) {
  return configuration.getMapper(type, this);
}
public  T getMapper(Class type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}
public  T getMapper(Class type, SqlSession sqlSession) {
  final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}

可以看到getMapper方法实际是调用MapperRegistry的getMapper方法,MapperRegistry我们在前面已经分析过,它保存了Mapper接口与MapperProxyFactory的映射。通过Mapper接口取得对应的MapperProxyFactory,此类的目的是为了创建  Mapper接口的代理对象MapperProxy,对代理对象的调用委托给了MapperMethod对象的execute方法,具体过程在我前面分析过的一篇文章《Mapper映射的解析过程》里,这里不再叙述。 我们详细分析一下MapperMethod。

private final SqlCommand command;
private final MethodSignature method;

public MapperMethod(Class mapperInterface, Method method, Configuration config) {
  this.command = new SqlCommand(config, mapperInterface, method);
  this.method = new MethodSignature(config, mapperInterface, method);
}

通过MapperMethod的构造方法传入了Mapper的接口类,所有执行的Mapper方法和Mybatis配置中心。构造方法中会新创建两个对象SqlCommand和MethodSignature。下面分析一下这两个对象具体是什么东西。

SqlCommand:这个类会保存两个成员变量,一个是MappedStatement的id属性,一个是所代表执行sql的类型。

public SqlCommand(Configuration configuration, Class mapperInterface, Method method) {
  final String methodName = method.getName();
  final Class declaringClass = method.getDeclaringClass();
  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 {
    name = ms.getId();
    type = ms.getSqlCommandType();
    if (type == SqlCommandType.UNKNOWN) {
      throw new BindingException("Unknown execution method for: " + name);
    }
  }
}

内部有一个resolveMappedStatement方法,此方法返回在解析xml映射文件保存在Configuration中的MappedStatement,将其id属性保存赋值给name成员变量。将其sqlCommandType赋值给type成员变量。

MethodSignature:包含了关于执行的Mapper方法的参数类型和返回类型。

public MethodSignature(Configuration configuration, Class mapperInterface, Method method) {
  Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
  if (resolvedReturnType instanceof Class) {
    this.returnType = (Class) resolvedReturnType;
  } else if (resolvedReturnType instanceof ParameterizedType) {
    this.returnType = (Class) ((ParameterizedType) resolvedReturnType).getRawType();
  } else {
    this.returnType = method.getReturnType();
  }
  this.returnsVoid = void.class.equals(this.returnType);
  this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
  this.returnsCursor = Cursor.class.equals(this.returnType);
  this.mapKey = getMapKey(method);
  this.returnsMap = this.mapKey != null;
  this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
  this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
  this.paramNameResolver = new ParamNameResolver(configuration, method);
}

其中包括方法的返回类型,返回类型是否为void,返回类型是否为Collection或数组或游标,如果参数中含有RowBounds类型保存其在参数列表的索引值,ResultHandler同理,最后还new了一个ParamNameResolver对象,MethodSignature通过此对象可以对外提供获取参数索引值或参数名的方法,因为此对象内部缓存了方法参数列表中除了RowBounds和ResultHandler类型参数的索引值与参数名的键值对。举个列子:

  • aMethod(@Param("M") int a, @Param("N") int b) -> {{0, "M"}, {1, "N"}}
  • aMethod(int a, int b) -> {{0, "0"}, {1, "1"}}
  • aMethod(int a, RowBounds rb, int b) -> {{0, "0"}, {2, "1"}}
  • 其中参数名由@Param注解指定若没有此注解会通过java8的Parameter的获取真实参数名,前提Mapper类是使用java8编译的并且开启了--parameters编译选项。否则参数名为arg0,arg1...的形式了。

    接下来着重讲一下execute方法。

    public Object execute(SqlSession sqlSession, Object[] args) {
      Object result;
      switch (command.getType()) {
        case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
          result = rowCountResult(sqlSession.insert(command.getName(), param));
          break;
        }
        case UPDATE: {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = rowCountResult(sqlSession.update(command.getName(), param));
          break;
        }
        case DELETE: {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = rowCountResult(sqlSession.delete(command.getName(), param));
          break;
        }
        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;
        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;
    }

    可以看到execute的执行分为增删改查和刷新,除了FLUSH类型操作,都会调用MethodSignature的convertArgsToSqlCommandParam方法将Object[] args的参数类型转换为null,或者单个参数对象或ParamMap三种类型中的一种。

    public Object convertArgsToSqlCommandParam(Object[] args) {
      return paramNameResolver.getNamedParams(args);
    }
    public Object getNamedParams(Object[] args) {
      final int paramCount = names.size();
      if (args == null || paramCount == 0) {
        return null;
      } else if (!hasParamAnnotation && paramCount == 1) {
        return args[names.firstKey()];
      } else {
        final Map param = new ParamMap();
        int i = 0;
        for (Map.Entry entry : names.entrySet()) {
          param.put(entry.getValue(), args[entry.getKey()]);
          // add generic param names (param1, param2, ...)
          final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
          // ensure not to overwrite parameter named with @Param
          if (!names.containsValue(genericParamName)) {
            param.put(genericParamName, args[entry.getKey()]);
          }
          i++;
        }
        return param;
      }
    } 
      

    如果参数数组中只有一个参数且没有标记@Param则将参数转换为args[0],否则将参数数组转换为一个map其中,key为参数名(也可能是arg0,arg1) value为原始参数对象,并且还为多提供一套k如param1,param2的键值对组合。下面我们以SELECT类型详细分析一下execute方法。

    SELECT类型一个有5个分支,我们分析最常用的三种:

    1.返回类型为Collection或数组

    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;
    }

    可以看到这个方法最终调用了sqlSession的selectList方法。然后在将结果转换为数组或相应的Collection。

    2.返回类型是Map

    private  Map executeForMap(SqlSession sqlSession, Object[] args) {
      Map result;
      Object param = method.convertArgsToSqlCommandParam(args);
      if (method.hasRowBounds()) {
        RowBounds rowBounds = method.extractRowBounds(args);
        result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds);
      } else {
        result = sqlSession.selectMap(command.getName(), param, method.getMapKey());
      }
      return result;
    }

    可以看到是调用了sqlSession的selectMap,而selectMap内部其实也是调用了它的selectList方法。

    3.返回类型为一个普通对象

    public  T selectOne(String statement, Object parameter) {
      // Popular vote was to return null on 0 results and throw exception on too many.
      List list = this.selectList(statement, parameter);
      if (list.size() == 1) {
        return list.get(0);
      } else if (list.size() > 1) {
        throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
      } else {
        return null;
      }
    }

    所以这三种返回类型的查询操作核心就是SqlSession的selectList方法,MapperMethod将SqlCommand的那么属性,通过MethodSignature转换后的参数对象或ParamMap和RowBounds对象(可为空)传入SqlSession的selectList方法执行后再将结果转换为MethodSignature的returnType指定的类型。

    下一篇分析DefaultSqlSession的selectList方法。

    你可能感兴趣的:(Mybatis源码分析)