Mybatis 方法入参解析

我们在使用Mybatis时,Mapper接口的方法一般都是长这样

int updatePassword(@Param("id") Integer id, @Param("oldPwd") String oldPwd, @Param("newPwd") String newPwd);

对应的Mapper.xml 如下

  <update id="updatePassword">
    update t_user
    set password = #{newPwd}
    where id = #{id} and password = #{oldPwd}
  </update>

有时候可能应为某个参数的 @Param 注解没有写,或者因为其他原因导致SQL执行报错
org.apache.ibatis.binding.BindingException: Parameter ‘newPwd’ not found. Available parameters are [0, 1, 2, param3, param1, param2]

下面来看看Mybatis 到底是怎么样解析参数的

解析Mybatis入参的类主要为 ParamNameResolver

  • org.apache.ibatis.reflection.ParamNameResolver
    直接上源码吧, 看看究竟是什么样的逻辑
public class ParamNameResolver {

  private static final String GENERIC_NAME_PREFIX = "param";

  /**
   * <p>
   * The key is the index and the value is the name of the parameter.<br />
   * The name is obtained from {@link Param} if specified. When {@link Param} is not specified,
   * the parameter index is used. Note that this index could be different from the actual index
   * when the method has special parameters (i.e. {@link RowBounds} or {@link ResultHandler}).
   * </p>
   * <ul>
   * <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>
   * </ul>
   */
  private final SortedMap<Integer, String> names;

  private boolean hasParamAnnotation;
	
	// 解析参数入参名
	// 此方法解析完的样子 
	// 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"}}
  public ParamNameResolver(Configuration config, Method method) {
  	// method 即当前执行的Mapper中的方法
  	// 获取方法参数类型的数组
    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])) {
        continue;
      }
      String name = null;
      // 查看当前循环的参数是否有@Param 注解修饰
      for (Annotation annotation : paramAnnotations[paramIndex]) {
        if (annotation instanceof Param) {
          hasParamAnnotation = true;
          // 如果有@Param 注解修饰,则获取其value值,赋值给name
          name = ((Param) annotation).value();
          break;
        }
     }
      if (name == null) {
      // 如果name == null, 则看Mybatis 是否 开启了useActualParamName 配置(默认是true)
        if (config.isUseActualParamName()) {
        	//如果开启, 则使用方法签名中的名称作为语句参数名称; 即Mapper 方法的形参名
          name = getActualParamName(method, paramIndex);
        }
        if (name == null) {
          // use the parameter index as the name ("0", "1", ...)
          // 如果name 还是为空,则使用当前参数在参数列表中的下标作为 name ("0","1","2",...)
          name = String.valueOf(map.size());
        }
      }
      // 将当前参数在参数列表中的下标和上面获取到的 name 放入map中,然后开始下一个参数的处理
      map.put(paramIndex, name);
    }
    names = Collections.unmodifiableSortedMap(map);
  }

  private String getActualParamName(Method method, int paramIndex) {
    return ParamNameUtil.getParamNames(method).get(paramIndex);
  }

  private static boolean isSpecialParameter(Class<?> clazz) {
    return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
  }

  /**
   * Returns parameter names referenced by SQL providers.
   */
  public String[] getNames() {
    return names.values().toArray(new String[0]);
  }


	// 获取参数值
	// 返回的参数可能的样子
	// null
	// 一个Object对象 (方法只有一个入参,且还没有使用@param来修饰这个参数时)
	// 一个map 
	// {{"id",1},{"oldPwd","123"},{"newPwd","456"},{"param1","1"},{"param2","123"},{"param3","456"}} 每个参数都使用了 @param 修饰,
	// {{"1",1},{"2","123"},{"3","456"},{"param1","1"},{"param2","123"},{"param3","456"}}
	// 每个参数都没有使用 @param 修饰, 且useActualParamName 设置为false (or jdk <1.8)
  public Object getNamedParams(Object[] args) {
  // 拿到上面解析生成name,获取其size大小
    final int paramCount = names.size();
    // 如果方法没有传入参数,则返回null
    if (args == null || paramCount == 0) {
      return null;
      // 如果只传入了1个参数,并且还没有使用@param 注解修饰这个参数
    } else if (!hasParamAnnotation && paramCount == 1) {
    	// 则直接将这个参数返回
      return args[names.firstKey()];
    } else {
    	// 否则,先创建一个map来封装 参数名 和 参数值
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;
      // 遍历names
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
      	// 将names中的value (@param 传入的,或0,1,2这些) 和 对应的实参 放入 map中
        param.put(entry.getValue(), args[entry.getKey()]);
       	// param1, param2
        final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
        // 如果有重复的key名,将被跳过,避免覆盖
        if (!names.containsValue(genericParamName)) {
         // 同时还添加 (param1, param2, ...) 这样的key 和对应的实参, 也一同放入map中
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }
}

如果你的JDK 在1.8 或以上,且Mybatis全局配置文件中useActualParamName 配置为true,(默认是true) , 则可以在入参前不加@param注解修饰. 在SQL中,也能使用#{id},这样的形式绑定参数.

	<settings>
		<setting name="useActualParamName" value="true"/>
		<setting name="mapUnderscoreToCamelCase" value="true"/>
		<setting name="jdbcTypeForNull" value="NULL"/>
	</settings>
int updatePassword(Integer id,String oldPwd,String newPwd);

对应的Mapper.xml 如下

  <update id="updatePassword">
    update t_user
    set password = #{newPwd}
    where id = #{id} and password = #{oldPwd}
  </update>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值