mybatis底层组件介绍-binding(二)

本文深入探讨MyBatis中的SqlCommand、MethodSignature和MapperMethod类,它们在处理Mapper接口的执行过程中起到核心作用。SqlCommand负责记录SQL语句和类型,MethodSignature存储方法信息如返回类型和参数细节,MapperMethod整合这两者,执行不同类型的SQL操作。文章详细解析了这些类的属性、方法及其实现逻辑,揭示了MyBatis如何动态执行Mapper接口的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

binging

本篇接着上篇继续讲binding模块中的其他类,比如MapperMethod,SqlCommand等

SqlCommand

SqlCommand主要用来记录sql代码块的名称和sql类型,是通过Mapper接口全路径名和执行的接口名称在全局配置文件中找到对应的MappedStatement来实现的

重要属性

// 对应着MappedStatement的id
private final String name;
// 当前执行的sql的类型
private final SqlCommandType type;

重要方法

构造函数

下面看下SqlCommand

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);
    }
  }
}
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
        Class<?> declaringClass, Configuration configuration) {
  // 使用接口的全路径名和方法名称作为sql代码块的唯一标志
  String statementId = mapperInterface.getName() + "." + methodName;
  // 从全局配置信息中使用唯一标志来获取对应的MappedStatement
  if (configuration.hasStatement(statementId)) {
    return configuration.getMappedStatement(statementId);
  } else if (mapperInterface.equals(declaringClass)) {
    return null;
  }
  // 当前执行的方法并不是在当前的mapperInterface中定义的,尝试从父接口中寻找该方法,然后使用父接口全路径名加方法名称作为sql代码块的唯一标志
  for (Class<?> superInterface : mapperInterface.getInterfaces()) {
    if (declaringClass.isAssignableFrom(superInterface)) {
      MappedStatement ms = resolveMappedStatement(superInterface, methodName,
          declaringClass, configuration);
      if (ms != null) {
        return ms;
      }
    }
  }
  return null;
}

MethodSignature

MethodSignature主要记录了当前执行的方法的信息,比如方法的返回值类型、是否返回Map、是否返回void等信息

重要属性

// 根据方法的返回值类型设置一些标志变量
// 方法的返回值类型是否是集合类
private final boolean returnsMany;
// 方法是否使用了MapKey注解来指定返回的多条记录使用哪个列作为key
private final boolean returnsMap;
// 方法的返回值类型是否是void
private final boolean returnsVoid;
// 方法的返回值类型是否是cursor
private final boolean returnsCursor;
// 方法的返回值类型是否是optional
private final boolean returnsOptional;
// 接口返回值类型
private final Class<?> returnType;
// 使用@MapKey指定的map中的key是查询结果中的哪一列
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
private final ParamNameResolver paramNameResolver;

重要方法

构造方法
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) {
    // 如果返回值类型是具有泛型的类,那么返回最外层的类,比如List<Integer>,这里就会解析成List
    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.returnsOptional = Optional.class.equals(this.returnType);
  // 这里会判断方法是否使用了@MapKey注解,如果使用了,那么这里的mapKey就是该注解中的value
  this.mapKey = getMapKey(method);
  // 这里需要注意,判断方法是否返回map,并不是根据返回值类型来判断的,而是根据方法是否使用了@MapKey注解来判断的
  this.returnsMap = this.mapKey != null;
  // 遍历方法的参数,如果参数中有RowBounds类型的参数,那么这里记录的就是该参数出现的位置
  this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
  // 遍历方法的参数,如果参数中有ResultHandler类型的参数,那么这里记录的就是该参数出现的位置
  this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
  // paramNameResolver主要用来维护参数出现的位置和参数的名称之间的映射
  this.paramNameResolver = new ParamNameResolver(configuration, method);
}

ParamNameResolver

这里主要看下ParamNameResolver,ParamNameResolver主要用来解析Mapper接口方法中参数的位置和名称之间的映射关系
当方法中的参数使用了@Param注解时,会使用@Param注解的value作为该参数的名称,否则会使用下标来作为名称,这里需要注意当参数中出现了特殊的参数,比如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"}}

重要属性

// 参数下标和参数名称之间的映射
private final SortedMap<Integer, String> names;
private boolean hasParamAnnotation;
// 当没有使用@Param注解来指定参数的名称时,是否使用参数的真实名称作为names中使用的名称
private final boolean useActualParamName;

重要方法

构造方法
public ParamNameResolver(Configuration config, Method method) {
  this.useActualParamName = config.isUseActualParamName();
  // 获取当前方法参数的类型
  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++) {
    // RowBounds或者是ResultHandler会被识别为特殊类型,会直接跳过
    if (isSpecialParameter(paramTypes[paramIndex])) {
      // skip special parameters
      continue;
    }
    String name = null;
    // 判断参数是否使用了@Param注解,如果使用了,那么使用其value属性作为参数的名称
    for (Annotation annotation : paramAnnotations[paramIndex]) {
      if (annotation instanceof Param) {
        hasParamAnnotation = true;
        name = ((Param) annotation).value();
        break;
      }
    }
    if (name == null) {
      // @Param was not specified.
      // 如果没有使用@Param注解来指定名称,并且在配置文件中开启了使用真实名称作为参数名称,那么这里会使用参数的真实名称作为names这个map中的名称
      if (useActualParamName) {
        name = getActualParamName(method, paramIndex);
      }
      // 如果通过上面的步骤都没有解析出参数的名称,那么这里会使用当前names的大小作为参数的名称
      if (name == null) {
        // use the parameter index as the name ("0", "1", ...)
        // gcode issue #71
        name = String.valueOf(map.size());
      }
    }
    map.put(paramIndex, name);
  }
  names = Collections.unmodifiableSortedMap(map);
}

总结一下上面关于参数名称的解析步骤:

  1. 如果是特殊参数,比如RowBounds或者ResultHandler,那么直接跳过,不会解析其名称
  2. 使用了@Param注解,那么使用注解的value作为当前参数的名称
  3. 没有使用@Param注解,但是开启了useActualParamName配置,那么会使用参数的真实名称
  4. 上述步骤都没有解析出名称,那么使用当前names这个map的大小作为名称
convertArgsToSqlCommandParam
public Object convertArgsToSqlCommandParam(Object[] args) {
  return paramNameResolver.getNamedParams(args);
}

getNamedParams方法主要获取参数名称和参数值之间的映射,这里的参数名称就是通过上面的步骤解析出来的参数名称

public Object getNamedParams(Object[] args) {
 final int paramCount = names.size();
 if (args == null || paramCount == 0) {
   return null;
 } else if (!hasParamAnnotation && paramCount == 1) {
 	// 单独处理没有使用@Param注解,并且只有一个参数的情况
   Object value = args[names.firstKey()];
   return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
 } else {
   final Map<String, Object> param = new ParamMap<>();
   int i = 0;
   for (Map.Entry<Integer, String> entry : names.entrySet()) {
     param.put(entry.getValue(), args[entry.getKey()]);
     // add generic param names (param1, param2, ...)
     // 这里会另外为每个参数添加名称为param1,param2的参数映射关系
     final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
     // ensure not to overwrite parameter named with @Param
     if (!names.containsValue(genericParamName)) {
       param.put(genericParamName, args[entry.getKey()]);
     }
     i++;
   }
   return param;
 }
}

这里主要是处理集合类和数组类的参数,另外需要注意,这如果传入的是一个map,则会直接返回这个map,传入的map指定了参数名称和参数值之间的映射

public static Object wrapToMapIfCollection(Object object, String actualParamName) {
  if (object instanceof Collection) {
    ParamMap<Object> map = new ParamMap<>();
    map.put("collection", object);
    if (object instanceof List) {
      map.put("list", object);
    }
    Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
    return map;
  } else if (object != null && object.getClass().isArray()) {
    ParamMap<Object> map = new ParamMap<>();
    map.put("array", object);
    Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
    return map;
  }
  return object;
}

MapperMethod

从上一篇文章我们知道,当调用MapperProxy的invoke方法时,会将执行参数透传给底层的MapperMethod

重要属性

// 当前需要执行的sql信息,比如sql的类型,sql的statementId等
private final SqlCommand command;
// 当前执行方法的信息,比如参数的注解,参数的下标和参数名称之间的映射
private final MethodSignature method;

重要方法

execute

这里主要是根据不同的sql类型执行不同的操作,具体每个操作的源码等之后再详细看

public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  switch (command.getType()) {
  	// 根据sql类型的不同执行不同的操作
    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);
        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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值