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

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

<T> T getMapper(Class<T> type);

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

public <T> T getMapper(Class<T> type) {
  return configuration.<T>getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) 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类型参数的索引值与参数名的键值对。举个列子:

<li>aMethod(@Param("M") int a, @Param("N") int b) -&gt; {{0, "M"}, {1, "N"}}</li>
<li>aMethod(int a, int b) -&gt; {{0, "0"}, {1, "1"}}</li>
<li>aMethod(int a, RowBounds rb, int b) -&gt; {{0, "0"}, {2, "1"}}</li>

其中参数名由@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<String, Object> param = new ParamMap<Object>();
    int i = 0;
    for (Map.Entry<Integer, String> 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 <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;
}

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

2.返回类型是Map

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);
    result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);
  } else {
    result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
  }
  return result;
}

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

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

public <T> T selectOne(String statement, Object parameter) {
  // Popular vote was to return null on 0 results and throw exception on too many.
  List<T> list = this.<T>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方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值