原创

MyBatis源码解析 - binding模块

前言

在iBatis(MyBatis的前身)中,查询一个Blog对象时需要调用SqlSession.queryForObject("selectBlog",blogId)方法。其中,SqlSession.queryForObject()方法 会执行指定的SQL语句进行查询并返回一个结果对象,第一个参数“selectBlog"指明了具体执行的SQL语句的id,该SQL语句定义在相应的映射配置文件中。如果我们错将“selectBlog” 写成了“selectBlog1”,在初始化过程中,MyBatis是无法提示该错误的,而在实际调用queryForObject("selectBlog1", blogld)方法时才会抛出异常,开发人员才能知道该错误。

MyBatis提供了binding模块用于解决上述问题,我们可以定义一个接口(为方便描述,后面统一一称为“Mapper接口”),该示例中为BlogMapper接口,具体代码如下所示。注意,这里的BlogMapper接口并不需要继承任何其他接口,而且开发人员不需要提供该接口的实现。binding模块对应的包在org.apache.ibatis.binding,下。

在开始分析binding模块的实现之前,先来了解一下该模块中核心组件之间的关系,如图所示。

MapperRegistry & MapperProxyFactory

MapperRegistryMapper接口及其对应的代理对象工厂的注册中心。ConfigurationMyBatis全局性的配置对象,在MyBatis初始化的过程中,所有配置信息会被解析成相应的对象并记录到Configuration对象中,在第3章介绍MyBatis初始化过程时会详细介绍Configuration
这里关注Configuration.mapperRegistry字段,它记录当前使用的MapperRegistry对象,MapperRegistry中字段的含义和功能如下:

//Configuration对象,MyBatis全局唯一的配置对象,其中包含全部的配置信息
private final Configuration config;
// 检测真正的数据库连接是否已经关闭
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

MyBatis初始化过程中会读取映射配置文件以及Mapper接口中的注解信息,并调用
MapperRegistry.addMapper()方法填充MapperRegistry.knownMappers集合,该集合的key是Mapper接口对应的Class对象,value 为MapperProxyFactory工厂对象,可以为Mapper接口创建代理对象,MapperProxyFactory的实现马上就会分析到。MapperRegistry.addMapper()方法的部分实现如下:

public <T> void addMapper(Class<T> type) {
  //检测type是否是接口
  if (type.isInterface()) {
    //检测是否已经加载过该接口
    if (hasMapper(type)) {
      throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
    }
    boolean loadCompleted = false;
    try {
      //将Mapper接口对应的Class对象和MapperProxyFactory对象添加到knownMappers集合
      knownMappers.put(type, new MapperProxyFactory<>(type));
      // It's important that the type is added before the parser is run
      // otherwise the binding may automatically be attempted by the
      // mapper parser. If the type is already known, it won't try.
      MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      //注解解析 注解处理
      parser.parse();
      loadCompleted = true;
    } finally {
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}

在需要执行某SQL语句时,会先调用MapperRegistry.getMapper()方法获取实现了Mapper接口的代理对象,例如本节开始的示例中,session.getMapper(BlogMapper.cass)方法得到的实际上是MyBatis通过JDK动态代理为BlogMapper接口生成的代理对象。MapperRegistry.getMapper()方法的代码如下所示。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  //查找指定type对应的MapperProxyFactory对象
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  // ...如果mapperProxyFactory为空,则抛出异常(略)
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
    //创建实现了type接口的代理对象
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}

MapperProxyFactory主要负责创建代理对象,其中核心字段的含义和功能如下:

//当前MapperProxyFactory对象可以创建实现了mapperInterface接口的代理对象,在本节开始的示例中,
//就是BlogMapper接口对应的Class对象
private final Class<T> mapperInterface;

//缓存,key是mapperInterface接口中某方法对应的Method对象,value 是对应的MapperMethod对象
// MapperMethod 的功能和实现马上就会分析到
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

MapperProxyFactory.newlnstance()方法实现了创建实现了mapperInterface接口的代理对象的功能,具体代码如下:

protected T newInstance(MapperProxy<T> mapperProxy) {
  //创建实现了mapperInterface接口的代理对象
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
  //创建MapperProxy对象,每次调用都会创建新的MapperProxy对象
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}

MapperProxy

MapperProxy实现了InvocationHandler接口,在介绍JDK动态代理时已经介绍过,该接口的实现是代理对象的核心逻辑,这里不再重复描述。MapperProxy中核心字段的含义和功能如下:

private final SqlSession sqlSession;  //记录了关联的sqlSession对象
private final Class<T> mapperInterface; //Mapper接口对应的Class对象
//用于缓存MapperMethod对象,其中key是Mapper接口中方法对应的Method对象,value 是对应的
// MapperMethod 对象。MapperMethod 对象会完成参数转换以及SQL语句的执行功能
//需要注意的是,MapperMethod中并不记录任何状态相关的信息,所以可以在多个代理对象之间共享
private final Map<Method, MapperMethodInvoker> methodCache;

MapperProxy.invoke()方法是代理对象执行的主要逻辑,实现如下:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    //如果目标方法继承自object,则直接调用目标方法
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    } else {
      //从缓存中获取MapperMethodInvoker对象 如果没有则创建新的MapperMethodInvoker对象并添加到缓存中
      return cachedInvoker(proxy, method, args).invoke(proxy, method, args, sqlSession);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
}

MapperProxy.cachedInvoker(proxy, method, args)主要维护methodCache这个缓存集合,实现如下:

private MapperMethodInvoker cachedInvoker(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    return methodCache.computeIfAbsent(method, m -> {
      if (m.isDefault()) {
        try {
          if (privateLookupInMethod == null) {
            //对于JDK1.8 调用处理
            return new DefaultMethodInvoker(getMethodHandleJava8(method));
          } else {
            //对于JDK9 调用处理
            return new DefaultMethodInvoker(getMethodHandleJava9(method));
          }
        } catch (IllegalAccessException | InstantiationException | InvocationTargetException
            | NoSuchMethodException e) {
          throw new RuntimeException(e);
        }
      } else {
        //其他版本处理
        return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
      }
    });
  } catch (RuntimeException re) {
    Throwable cause = re.getCause();
    throw cause == null ? re : cause;
  }
}

MapperMethod

MapperMethod中封装了Mapper接口中对应方法的信息,以及对应SQL语句的信息。读者可以将MapperMethod看作连接Mapper接口以及映射配置文件中定义的SQL语句的桥梁。MapperMethod中各个字段的信息如下:

private final SqlCommand command; //记录了SQL语句的名称和类型
private final MethodSignature method; //Mapper接口中对应方法的相关信息

SqlCommand

SqlCommandMapperMethod中定义的内部类,它使用name字段记录了SQL语句的名称,使用type字段(SqlCommandType类型)记录了SQL语句的类型。SqlCommandType是枚举类型,有效取值为UNKNOWNINSERTUPDATEDELETESELECTFLUSH
SqlCommand的构造方法会初始化name字段和type字段,代码如下:

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
  //获取方法名
  final String methodName = method.getName();
  final Class<?> declaringClass = method.getDeclaringClass();
  //从Configuration. mappedStatements集合中查找对应的MappedStatement对象
  MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
      configuration);
  if (ms == null) {
    //处理@Flush注解
    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和type
    name = ms.getId();
    type = ms.getSqlCommandType();
    if (type == SqlCommandType.UNKNOWN) {
      throw new BindingException("Unknown execution method for: " + name);
    }
  }
}
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
        Class<?> declaringClass, Configuration configuration) {
      // SQL 语句的名称是由Mapper接口的名称与对应的方法名称组成的
      String statementId = mapperInterface.getName() + "." + methodName;
      if (configuration.hasStatement(statementId)) {  //检测是否有该名称的SQL语句
        //从Configuration. mappedStatements集合中查找对应的MappedStatement对象,
        // MappedStatement 对象中封装了SQL语句相关的信息,在MyBatis初始化时创建,后面详细描述
        return configuration.getMappedStatement(statementId);
      } else if (mapperInterface.equals(declaringClass)) {
        return null;
      }
      //如果指定方法是在父接口中定义的,则在此进行继承结构的处理
      //采用递归方式 从Configuration. mappedStatements集合中查找对应的MappedStatement对象,
      for (Class<?> superInterface : mapperInterface.getInterfaces()) {
        if (declaringClass.isAssignableFrom(superInterface)) {
          MappedStatement ms = resolveMappedStatement(superInterface, methodName,
              declaringClass, configuration);
          if (ms != null) {
            return ms;
          }
        }
      }
      return null;
    }
}

ParamNameResolver

MethodSignature中,会使用ParamNameResolver处理Mapper接口中定义的方法的参数列表。ParamNameResolve使用name字段(SortedMap< Integer, String>类型) 记录了参数在参数列表中的位置索引与参数名称之间的对应关系,其中key表示参数在参数列表中的索引位置,value表示参数名称,参数名称可以通过@Param注解指定,如果没有指定@Param注解,则使用参数索引作为其名称。如果参数列表中包含RowBounds类型或ResultHandler类型的参数,则这两种类型的参数并不会被记录到name集合中,这就会导致参数的索引与名称不一致,例如,method(int a, RowBounds rb, int b)方法对应的names集合为{{0, "0"}, {2, "1"}},如图所示。

ParamNameResolverhasParamAnnotation字段 boolean类型)记录对应方法的参数列表中是否使用了@Param注解。.
ParamNameResolver的构造方法中,会通过反射的方式读取Mapper接口中对应方法的信息,并初始化.上述两个字段,具体实现代码如下:

public ParamNameResolver(Configuration config, Method method) {
  //获取参数列表每个参数的类型
  final Class<?>[] paramTypes = method.getParameterTypes();
  //获取参数列表上的注解
  final Annotation[][] paramAnnotations = method.getParameterAnnotations();
  //该集合用于记录参数索引与参数名称的对应关系
  final SortedMap<Integer, String> map = new TreeMap<>();
  int paramCount = paramAnnotations.length;
  // get names from @Param annotations
  //遍历方法的所有参数
  for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
    if (isSpecialParameter(paramTypes[paramIndex])) {
      // skip special parameters
      //如果参数是RowBounds类型或ResultHandler类型,则跳过对该参数的分析
      continue;
    }
    String name = null;
    //遍历该参数对应的注解集合
    for (Annotation annotation : paramAnnotations[paramIndex]) {
      // @Param注解出现过一次,就将hasParamAnnotation初始化为true
      if (annotation instanceof Param) {
        hasParamAnnotation = true;
        name = ((Param) annotation).value();
        break;
      }
    }
    if (name == null) {
      // @Param was not specified.
      //该参数没有对应的@Param注解,则根据配置决定是否使用参数实际名称作为其名称
      if (config.isUseActualParamName()) {
        name = getActualParamName(method, paramIndex);
      }
      if (name == null) {
        // use the parameter index as the name ("0", "1", ...)
        // gcode issue #71
        //使用参数的索引作为其名称
        name = String.valueOf(map.size());
      }
    }
    //记录到map集合中保存
    map.put(paramIndex, name);
  }
  //初始化names集合
  names = Collections.unmodifiableSortedMap(map);
}
// isSpecialParameter() 方法用来过滤RowBounds和ResultHandler两种类型的参数
private static boolean isSpecialParameter(Class<?> clazz) {
    return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
}

names集合主要在ParamNameResolver. getNamedParams()方法中使用,该方法接收的参数是用户传入的实参列表,并将实参与其对应名称进行关联,具体代码如下:

public Object getNamedParams(Object[] args) {
  final int paramCount = names.size();
  if (args == null || paramCount == 0) {  //无参数 返回null
    return null;
  } else if (!hasParamAnnotation && paramCount == 1) {  //未使用@Param且只有一个参数
    return args[names.firstKey()];
  } else {  //处理使用@Param注解指定了参数名称或有多个参数的情况
    // param这个Map中记录了参数名称与实参之间的对应关系。ParamMap继承了HashMap, 如果向
    // ParamMap 中添加已经存在的key,会报错,其他行为与HashMap相同
    final Map<String, Object> param = new ParamMap<>();
    int i = 0;
    for (Map.Entry<Integer, String> entry : names.entrySet()) {
      //将参数名与实参对应关系写入param中
      param.put(entry.getValue(), args[entry.getKey()]);
      // add generic param names (param1, param2, ...)
      //下面是为参数创建"param+索引"格式的默认参数名称,例如: paraml, param2 等,并添加
      //到param集合中
      final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
      // ensure not to overwrite parameter named with @Param
      //如果@Param注解指定的参数名称就是"param+索引"格式的,则不需要再添加
      if (!names.containsValue(genericParamName)) {
        param.put(genericParamName, args[entry.getKey()]);
      }
      i++;
    }
    return param;
  }
}

MethodSignature

MethodSignature也是MapperMethod中定义的内部类,其中封装了Mapper接口中定义的方法的相关信息,MethodSignature中核心字段的含义如下:

private final boolean returnsMany;   //返回值类型是否为Collection类型或是数组类型
private final boolean returnsMap;     //返回值类型是否是Map类型
private final boolean returnsVoid;    //返回值类型是否为Void
private final boolean returnsCursor;  //返回值类型是否是cursor类型
private final boolean returnsOptional;  //返回值类型是否是Optional类型
private final Class<?> returnType;    //返回类型
private final String mapKey;  // SQL 语句的名称是由Mapper接口的名称与对应的方法名称组成的
private final Integer resultHandlerIndex; //用来标记该方法参数列表中ResultHandler类型参数的位置
private final Integer rowBoundsIndex; //用来标记行边界的位置
private final ParamNameResolver paramNameResolver;  //该方法对应的ParamNameResolver对象

MethodSignature的构造函数中会解析相应的Method对象,并初始化上述字段,具体代码如下:

public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
  //解析方法的返回值类型,前面已经介绍过TypeParameterResolver的实现,这里不再赘述
  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();
  }
  //初始化 returnsVoid, returnsMany. returnsCursor. mapKey. returnsMap 等字段
  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.returnsOptional = Optional.class.equals(this.returnType);
  //若MethodSignature对应方法的返回值是Map且指定了eMapKey注解,则使用getMapKey()方法处理
  this.mapKey = getMapKey(method);
  this.returnsMap = this.mapKey != null;
  //初始化rowBoundsIndex和resultHandlerIndex字段
  this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
  this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
  //创建ParamNameResolver对象
  this.paramNameResolver = new ParamNameResolver(configuration, method);
}

getUniqucParamIndex()方法的主要功能是查找指定类型的参数在参数列表中的位置,如下:

private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
  Integer index = null;
  final Class<?>[] argTypes = method.getParameterTypes();
  for (int i = 0; i < argTypes.length; i++) {   //遍历MethodSignature对应方法的参数列表
    if (paramType.isAssignableFrom(argTypes[i])) {
      if (index == null) {      //记录paramType类型参数在参数列表中的位置索引
        index = i;
      } else {    // RowBounds和ResultHandler类型的参数只能有一个,不能重复出现
        throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
      }
    }
  }
  return index;
}

MethodSignature中还提供了上述字段对应的getter/setter方法,不再赘述。其中,
convertArgsToSqlCommandParam()辅助方法简单介绍一下:

//负责将args[]数组(用户传入的实参列表)转换成sQL语句对应的参数列表,它是通过上面介绍的
public Object convertArgsToSqlCommandParam(Object[] args) {
  return paramNameResolver.getNamedParams(args);
}

介绍完MapperMethod中定义的内部类,回到MapperMethod继续分析。MapperMethod中最核心的方法是execute()方法,它会根据SQL语句的类型调用SqlSession对应的方法完成数据库操作。SqlSessionMyBatis的核心组件之一,其具体实现后面会详细介绍,这里读者暂时只需知道它负责完成数据库操作即可。MapperMethod.excute()方法的具体实现如下:

public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  switch (command.getType()) { //根据SQL语句的类型调用SqlSession对应的方法
    case INSERT: {
      //使用ParamNameResolver处理args[]数组(用户传入的实参列表)。将用户传入的实参与
      // 指定参数名称关联起来
      Object param = method.convertArgsToSqlCommandParam(args);
      //下面是为参数创建"param+索引"格式的默认参数名称,例如: paraml, param2 等,并添加
      //到param集合中
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
    case UPDATE: {
      // UPDATE和DELETE类型的sQL语句的处理与INSERT类型的SQL语句类似,唯-的区别是调用了
      // Sqlsession 的update()方法和delete()方法
      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:
      //处理返回值为void且ResultSet通过ResultHandler处理的方法
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {  //处理返回值类型为集合或数组的方法
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {   //处理返回值类型为Map的方法
        result = executeForMap(sqlSession, args);
      } else if (method.returnsCursor()) {   //处理返回值类型为Cursor的方法
        result = executeForCursor(sqlSession, args);
      } else {  //处理返回值为单一对象的方法
        Object param = method.convertArgsToSqlCommandParam(args);
        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;
}

当执行INSERTUPDATEDELETE类型的SQL语句时,其执行结果都需要经过MapperMethod.rowCountResult()方法处理。SqlSession中的insert()等方法返回的是int值,rowCountResult()方法会将该int值转换成Mapper接口中对应方法的返回值,具体实现如下:

private Object rowCountResult(int rowCount) {
  final Object result;
  if (method.returnsVoid()) {   //Mapper接口中相应方法的返回值为void
    result = null;
  } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) { //Mapper接口相应方法的返回值为int或者Integer
    result = rowCount;
  } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) { //Mapper接口相应方法的返回值Long或者long
    result = (long)rowCount;
  } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) { //Mapper接口相应方法的返回值boolean或者Boolean
    result = rowCount > 0;
  } else {
    throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
  }
  return result;
}

如果Mapper接口中定义的方法准备使用ResultHandler处理查询结果集,则通过MapperMethod.executeWithResultHandler()方法处理,具体实现如下:

private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
  //获取sQL语句对应的Mappedstatement对象,MappedStatement申记录了SQL语句相关信息,
  //后面详细描述
  MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
  //当使用ResultHandler处理结果集时,必须指定ResultMap或ResultType
  if (!StatementType.CALLABLE.equals(ms.getStatementType())
      && void.class.equals(ms.getResultMaps().get(0).getType())) {
    throw new BindingException("method " + command.getName()
        + " needs either a @ResultMap annotation, a @ResultType annotation,"
        + " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
  }
  //转换实参列表
  Object param = method.convertArgsToSqlCommandParam(args);
  if (method.hasRowBounds()) { //检测参数列表中是否有RowBounds类型的参数
    //获取RowBounds对象,根锯Methodsignature.rowBoundsIndex字段指定位置,从args数组中
    //查找。获取ResultHandler对象的原理相同
    RowBounds rowBounds = method.extractRowBounds(args);
    //调用sqlsession.select()方法, 执行查询,并由指定的ResultHandler处理结果对象
    sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
  } else {
    sqlSession.select(command.getName(), param, method.extractResultHandler(args));
  }
}

如果Mapper接口中对应方法的返回值为数组或是Collction接口实现类,则通过MapperMethod.executeForMany()方法处理,具体实现如下:

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
  List<E> result;
  Object param = method.convertArgsToSqlCommandParam(args); //参数列表转换
  if (method.hasRowBounds()) {    //检测是否指定了RowBounds参数
    RowBounds rowBounds = method.extractRowBounds(args);
    //调用sqlSession.selectList()方法完成查询
    result = sqlSession.selectList(command.getName(), param, rowBounds);
  } else {
    result = sqlSession.selectList(command.getName(), param);
  }
  // issue #510 Collections & arrays support
  //将集合装换为数组或者Collection集合
  if (!method.getReturnType().isAssignableFrom(result.getClass())) {
    if (method.getReturnType().isArray()) {
      return convertToArray(result);
    } else {
      return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
    }
  }
  return result;
}

convertToDeclaredCollection()方法和convertToArray()方法的功能类似,主要负责将结果对象转换成Collction集合对象和数组对象,具体实现如下:

private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
  //使用前面介绍的objectFactory,通过反射方式创建集合对象
  Object collection = config.getObjectFactory().create(method.getReturnType());
  //创建MetaObject对象
  MetaObject metaObject = config.newMetaObject(collection);
  metaObject.addAll(list);  //实际上调用Collection.addAll()方法
  return collection;
}

//convertToArray()方法实现如下
private <E> Object convertToArray(List<E> list) {
  //获取数组元素类型
  Class<?> arrayComponentType = method.getReturnType().getComponentType();
  //创建数组对象
  Object array = Array.newInstance(arrayComponentType, list.size());
  if (arrayComponentType.isPrimitive()) { //将list中每一项都添加到数组中
    for (int i = 0; i < list.size(); i++) {
      Array.set(array, i, list.get(i));
    }
    return array;
  } else {
    return list.toArray((E[])array);
  }
}

如果Mapper接口中对应方法的返回值为Map类型,则通过MapperMethod.executeForMap()方法处理,具体实现如下:

private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
  Map<K, V> result;
  Object param = method.convertArgsToSqlCommandParam(args);   //转换实参列表
  if (method.hasRowBounds()) {
    RowBounds rowBounds = method.extractRowBounds(args);
    //调用sqlSession.selectMap()方法完成查询操作
    result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds);
  } else {
    result = sqlSession.selectMap(command.getName(), param, method.getMapKey());
  }
  return result;
}

executeForCursor()方法与executeForMap()方法类似,唯一区别就是调用了SqlSessionselectCursor()方法,这里不再赘述,感兴趣的读者请参考源码。

正文到此结束
该篇文章的评论功能已被站长关闭