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";

  /**
   * 

* The key is the index and the value is the name of the parameter.
* 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}). *

*
    *
  • 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; // 解析参数入参名 // 此方法解析完的样子 // 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>

你可能感兴趣的:(Mybatis,Mybatis)