MyBatis参数使用@Param注解获取不到自增id问题

2021-08-31 15:13:57 浏览数 (1)

一、背景

群里有个哥们分享了一个mybatis的小”坑“。

”分享一个菜鸡点:mybatis中使用@param注解后,要keyProperty=“注解名.id”,不然拿不到生成的主键值“

那么我们就要看@Param 在什么时候用?为啥不写参数名不行呢?

二、解析

2.1 官方文档大法

官方文档http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html

user类

代码语言:javascript复制
public class User {
   //...
   public User(Integer id, String username, int age) {
     //...
  }
//...
}

为了将结果注入构造方法,MyBatis 需要通过某种方式定位相应的构造方法。 在下面的例子中,MyBatis 搜索一个声明了三个形参的的构造方法,参数类型以 java.lang.Integer, java.lang.String 和 int 的顺序给出。

代码语言:javascript复制

当你在处理一个带有多个形参的构造方法时,很容易搞乱 arg 元素的顺序。

从版本 3.4.3 开始,可以在指定参数名称的前提下,以任意顺序编写 arg 元素。

为了通过名称来引用构造方法参数,你可以添加 @Param 注解,或者使用 '-parameters' 编译选项并启用 useActualParamName 选项(默认开启)来编译项目。

下面是一个等价的例子,尽管函数签名中第二和第三个形参的顺序与 constructor 元素中参数声明的顺序不匹配。

代码语言:javascript复制

如果存在名称和类型相同的属性,那么可以省略 javaType 。剩余的属性和规则和普通的 id 和 result 元素是一样的。

【1】什么情况下用@param注解

一、是参数的顺序和xml映射文件的顺序不匹配时。

二、是参数为两个对象且属性有重名的时候,mybatis无法感知是哪个类的属性。

2.2 源码大法

为什么设置了@Parm的值,keyProperty不加item前缀就不生效呢?(内容略长,要有心理准备哈哈)

源码参考这里

我们加注解

代码语言:javascript复制
void insert(@Param("item") CuxTodoItems cuxTodoItems);

对应xml

代码语言:javascript复制
        insert into cux_todo_items (user_id,todo_item_title,todo_item_content,priority)
        values ( #{item.userId},#{item.todoItemTitle},#{item.todoItemContent},#{item.priority});

先执行这里构造参数名解析对象

代码语言:javascript复制
  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
    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) {
          hasParamAnnotation = true;
          name = ((Param) annotation).value();
          break;
        }
      }
      if (name == null) {
        // @Param was not specified.
        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);
  }

执行插入语句需要执行到

org.apache.ibatis.binding.MapperMethod#execute

代码语言:javascript复制
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    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()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          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;
  }
代码语言:javascript复制
public Object convertArgsToSqlCommandParam(Object[] args) {
      return paramNameResolver.getNamedParams(args);
    }

底层调用

org.apache.ibatis.reflection.ParamNameResolver#getNamedParams

代码语言:javascript复制
public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    if (args == null || paramCount == 0) {
      return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
      return args[names.firstKey()];
    } else {
      final Map param = new ParamMap();
      int i = 0;
      for (Map.Entry entry : names.entrySet()) {
        param.put(entry.getValue(), args[entry.getKey()]);
        // add generic param names (param1, param2, ...)
        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;
    }
  } 
我们看到参数名解析这一块,如果有@param注解则取这个注解的值,否则根据参数的索引取参数名,如果取不到则映射为0,1... 

   
然后调用插入后 
 
执行对GeneratedKeys的处理 
org.apache.ibatis.executor.keygen.SelectKeyGenerator#processGeneratedKeys 
org.apache.ibatis.executor.statement.PreparedStatementHandler#update 
 
org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#processAfter 
org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#processBatch 
最重要的是两个函数 
 
  
第一个是:org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#getTypeHandlers 
 
判断keyProperties是否存在,如果存在类型是啥 
 
这里存在且类型为Integer 
 
执行org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#populateKeys填充完毕 
 
项目源码:git@github.com:chujianyun/MyBatisParam.git 
调用Insert的测试url: http://localhost:8080/MyBatisParam/cuxTodoItems/insert?userId=2&todoItemTitle=测试&todoItemContent=2&priority=1 
  
发现核心就是用了@Parm设置了名字为item后,如果只是设置基本属性不带item的话,从ObjectWrapper里找不到这个属性。 
如果不用注解啥情况呢? 
    
        insert into cux_todo_items (user_id,todo_item_title,todo_item_content,priority)
        values ( #{userId},#{todoItemTitle},#{todoItemContent},#{priority});
    
     
设置参数名解析 
org.apache.ibatis.reflection.ParamNameResolver#ParamNameResolver 
 
插入后调用获取类型解析器 
 
不设置@Param注解时,objectWrapper直接是一个Object,所以直接可以找到todoItemId属性。 
 
这是关键。 
调用Url:http://localhost:8080/MyBatisParam/cuxTodoItems/insert2?userId=2&todoItemTitle=测试&todoItemContent=2&priority=1 
三、总结 
遇到问题优先看官方文档,一般大多数用法都可以找到。 
另外通过源码调试时学习的一个非常重要方法,大家遇到类似问题可以通过调试来研究。 
  
 
 如果觉得本文对你有帮助,欢迎点赞,欢迎关注我,如果有补充欢迎评论交流,我将努力创作更多更好的文章。 
 另外欢迎加入我的知识星球,知识星球ID:15165241 一起交流学习。 
 https://t.zsxq.com/Z3bAiea  申请时标注来自CSDN。 

0 人点赞