> 文档中心 > 《从面试题来看源码》,单参数,多参数,如何正确使用 @param

《从面试题来看源码》,单参数,多参数,如何正确使用 @param

Mybatis Dao 接口中,单参数,多参数,如何正确使用 @Param?

答:单参数、多参数下,都可以用注解或不用注解。单参数,一般不用注解,用了注解 sql 语句参数名必须跟注解名称一致。多参数下,建议使用注解,方便后期调式,如果不用注解必须使用 0,1… 索引 或者 param1,param2…

源码分析

如何初始化,请看该篇文章《从面试题来看源码》,Dao 接口的工作原理

首先还是来看 MapperProxy 代理类调用的时候执行的 invoke 方法
MapperProxy.java

@Override  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 if (isDefaultMethod(method)) {        //对jdk7以上版本,动态语言的支持        return invokeDefaultMethod(proxy, method, args);      }    } catch (Throwable t) {      throw ExceptionUtil.unwrapThrowable(t);    }    //1️⃣从缓存中获取 MapperMethod对象,如果缓存中没有,则创建新的 MapperMethod对象并添加到缓存中    final MapperMethod mapperMethod = cachedMapperMethod(method);    //2️⃣调用 MapperMethod.execute ()方法执行 SQL 语 句    return mapperMethod.execute(sqlSession, args);  }

首先来看 1️⃣,在 MapperMethod 中封装了 Mapper 接口中对应方法的信息,以及对应 SQL 语句的信息
MapperMethod.java

 public MapperMethod(Class mapperInterface, Method method, Configuration config) {    //记录了 SQL语句的名称和类型    this.command = new SqlCommand(config, mapperInterface, method);    //Mapper 接 口中对应方法的相关信息    this.method = new MethodSignature(config, mapperInterface, method);  }

MethodSignature 中使用 ParamNameResolver 处理 Mapper 接口中定义方法的参数列表
ParamNameResolver.java

 public ParamNameResolver(Configuration config, Method method) {    //获取参数列表中每个参数的类型    final Class[] paramTypes = method.getParameterTypes();    //获取参数列表上的注解,第一维对应方法一共拥有的参数数量,第二维对应相应参数的注解    final Annotation[][] paramAnnotations = method.getParameterAnnotations();    ///该集合用于记录参数索引与参数名称的对应关系    final SortedMap map = new TreeMap();    int paramCount = paramAnnotations.length;    // get names from @Param annotations    //PS: 循环处理所有参数,第一层for循环参数列表,第二层for循环参数的注解集合    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {      如果参数是 RowBounds 类型或 ResultHandler 类型,则跳过对该参数的分析      if (isSpecialParameter(paramTypes[paramIndex])) {        // skip special parameters        continue;      }      String name = null;      //遍历该参数的注解集合      for (Annotation annotation : paramAnnotations[paramIndex]) {        //有@param注解        if (annotation instanceof Param) {          hasParamAnnotation = true;          //获取@Param注解指定的参数名称          name = ((Param) annotation).value();          break;        }      }      //如果没有注解      if (name == null) {        // @Param was not specified.        // useActualParamName==true时  即name = arg0 ...        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.put(paramIndex, name);    }    names = Collections.unmodifiableSortedMap(map);  }

如果没有用注解,names 的结构是这样

如果是使用注解,结构是这样

上面方法的参数列表已经处理完了,下面就要处理参数列表跟传入数值的对应关系了,该过程在开头 2️⃣中进行处理
MapperMethod.java

  public Object execute(SqlSession sqlSession, Object[] args) {    Object result;    //根据 SQL 语句的类型调用 SqlSession 对应的方法    switch (command.getType()) {      case INSERT: {      //负责将 args []数组( 用户传入的实 参列表)转换成 SQL 语句对应的参数列表      Object param = method.convertArgsToSqlCommandParam(args);        //...        break;      }      case UPDATE: {        //...      }      case DELETE: {        //...      }      case SELECT:        //...      case FLUSH:        //...      default:        throw new BindingException("Unknown execution method for: " + command.getName());    }    //...  }

convertArgsToSqlCommandParam () 方法通过 ParamNameResolver 对象的 getNamedParams 实现
ParamNameResolver.java

    final int paramCount = names.size();    if (args == null || paramCount == 0) {      return null;      //未使用@Param且只有一个参数    } else if (!hasParamAnnotation && paramCount == 1) {      return args[names.firstKey()];      //处理使用@Param注解指定了参数名称或有多个参数的情况    } else {      final Map param = new ParamMap();      int i = 0;      for (Map.Entry entry : names.entrySet()) {        //将参数名与实参对应关系记录到 param 中        param.put(entry.getValue(), args[entry.getKey()]);        // add generic param names (param1, param2, ...)        // 下面是为参数创建”param+索号”格式的默认参数名称,例如: param1, param2 等,并添加到param集合中        //PS:所以如果你不用注解的话,SQL中就得用param1,param2...        final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(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;    }  }

最后你会发现 param 中是这个样子

所以说:
多参数下,如果不用注解必须使用 0,1… 索引 或者 param1,param2…

以上就是对该面试题的源码分析。

关注我,给你看更多面试分析