mybatis--参数传递源码分析

单元测试:

SqlSession openSession = sqlSessionFactory.openSession();
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmpByIdAndLastName(1, "Tom");
第三行在执行断点调试时, 先来到一个名字叫mapperProxy.class

动态代理的InvocationHandler

public class MapperProxy<T> implements InvocationHandler, Serializable {}

然后看里面的invoke方法

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//有一个判断,当前方法声明的类是在object里面声明的, 直接放行.
  if (Object.class.equals(method.getDeclaringClass())) {
    try {
      return method.invoke(this, args);
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }
  //否则,把method包装成一mapperMethod
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  return mapperMethod.execute(sqlSession, args);
}

下面看看如何执行的, 我们断点进入execute

public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  //执行之前,先判断是什么类型的,对应的走增删改查的方法
  //每次调用之前,resulte就是返回值,会把你传过来的参数转化为sql能用的.
  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()) {//map
        result = executeForMap(sqlSession, args);
      } else if (method.returnsCursor()) {//游标
        result = executeForCursor(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);//返回单个对象
        //底层调用的还是sqlSession
        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;
}
看上面的代码, Object param=method.convertArgsToSqlCommandParam(args);

这个里面是: 

public Object convertArgsToSqlCommandParam(Object[] args) {
  return paramNameResolver.getNamedParams(args);
}
我们跳进来看, 会发现这么一个方法:getNamedParams
这一段就是处理的代码, 我们看到param1,param2.这一块就是把原来的值封装成了param1和param2

ParamNameResolver解析参数封装map的, 这一块的代码我们来大致走一走

/**
   * <p>
   * A single non-special parameter is returned without a name.<br />
   * Multiple parameters are named using the naming rule.<br />
   * In addition to the default names, this method also adds the generic names (param1, param2,
   * ...).
   * </p>
   */
  public Object getNamedParams(Object[] args) {
  //上手先获取了一个names.size(),而这个names里面是有值的:{0=id,1=lastName}, key就是0,1 value就是id和lastName. 这里我们就能看出来是调用的哪个mapper接口, 见下图. 那name是如何确定的,我们见图下面的分析
    final int paramCount = names.size();
    ...........
  }

这个name是我们ParamNameResolver的一个参数

private final SortedMap<Integer, String> names;
而这个参数值的确定, 我们来看这个类的构造器
1.获取每个标了param注解的param值:id ,lastname; 赋值给name
2.每次解析一个参数给map中保存信息:(key:参数索引,value:name的值)   
name的值:      标注了param注解:注解的值      
    没有标注:         

1>全局配置:useActualParamName(jdk1.8):name=参数名          2>name=map.size();相当于当前元素的索引,假如传入了第三个,没标注解,那就key是2,value也是2了{0=id, 1=lastName,2=2}

public ParamNameResolver(Configuration config, Method method) {
//先拿到所有的参数
  final Class<?>[] paramTypes = method.getParameterTypes();
  //以及参数的注解
  final Annotation[][] paramAnnotations = method.getParameterAnnotations();
  final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
  int paramCount = paramAnnotations.length;
  // get names from @Param annotations  开始标注参数的索引
  for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
    if (isSpecialParameter(paramTypes[paramIndex])) {
      // skip special parameters
      continue;
    }
    String name = null;
    for (Annotation annotation : paramAnnotations[paramIndex]) {
      if (annotation instanceof Param) {//如果当前参数的注解是param
        hasParamAnnotation = true;
        name = ((Param) annotation).value();//拿到param注解的value值
        break;
      }
    }
    if (name == null) {
      // @Param was not specified.
      if (config.isUseActualParamName()) {
        name = getActualParamName(method, paramIndex);//Jdk1.8
      }
      if (name == null) {
        // use the parameter index as the name ("0", "1", ...)
        // gcode issue #71
        name = String.valueOf(map.size());//没标注解,name就是map的size
      }
    }
    map.put(paramIndex, name);//map每确定一个参数,就会增大一下
  }
  names = Collections.unmodifiableSortedMap(map);
}
我们接着来看getNamedParams方法

参数args 是[1,"Tom"]

public Object getNamedParams(Object[] args) {
  final int paramCount = names.size();
  //参数为null,直接返回
  if (args == null || paramCount == 0) {
    return null;
  } else if (!hasParamAnnotation && paramCount == 1) {
  //如果只有一个元素,并且没有Param注解;args[0]: 单个参数直接返回
    return args[names.firstKey()];
  } else {
  //多个元素或者有Param标注
    final Map<String, Object> param = new ParamMap<Object>();
    int i = 0;
    //map保存数据,最终是要返回的
    //遍历names,names我们上面讲了, 在构造器的时候就确定好了 {0=id,1=lastName}  
    for (Map.Entry<Integer, String> entry : names.entrySet()) {
    
    //name集合的value作为key;names集合的key又作为取值的参考args[0]:args[1,"Tom"]
    //最终的效果: {id=args[0]:1,lastName=args[1]:Tom}
      param.put(entry.getValue(), args[entry.getKey()]);
      
      // add generic param names (param1, param2, ...)
      //额外的将每一个参数也保存到map中,使用新的key:param1...paramN
      //效果:有Param注解可以#{指定的key},或者#{param1}
      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;
  }
}

到这里,就ok了, 转成param1, value 了. 参数传递也就ok啦!!

这是多个参数写了param的,如果没有写的话, param是一个map类型, 那么key就会是它的序号作为名字了,所以建议多个参数的时候使用param,不然后面如果顺序反了,就会错了.方便动态sql使用吧.

另外还有一个,就是参数如果传的是list. 只有一个参数, 通常也就不适用param了.

我们来看一下源码

if (object instanceof List) {
  StrictMap<Object> map = new StrictMap<Object>();
  map.put("list", object);
  return map;
}

所以取的时候,应该是collection="list"


如果不用param呢,应该怎么写:

    @Select("select * from t_sign where is_delete=#{arg2} AND status=#{arg0} AND id=#{arg1}")
//    SignEntity selectBySignId(@Param("signId") String signId,@Param("status") String status);
    SignEntity selectBySignId( String status ,String signId,String delete);

我测试了好几种,最后得出的结论是: 取值必须得arg[i]来取,因为我们看过是上面的源码, 它的key值是索引. 并且取值顺序依赖的是:接口参数的顺序.

所以如果不用param的话, 我理解的是代码可读性不高,况且太过于依赖参数的顺序,不方便维护.

好了好了,就到这里了

猜你喜欢

转载自blog.csdn.net/kwy15732621629/article/details/79710346