前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MyBatis参数使用@Param注解获取不到自增id问题

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

作者头像
明明如月学长
发布2021-08-31 15:13:57
1.2K0
发布2021-08-31 15:13:57
举报
文章被收录于专栏:明明如月的技术专栏

一、背景

群里有个哥们分享了一个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=%E6%B5%8B%E8%AF%95&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=%E6%B5%8B%E8%AF%95&todoItemContent=2&priority=1 
三、总结 
遇到问题优先看官方文档,一般大多数用法都可以找到。 
另外通过源码调试时学习的一个非常重要方法,大家遇到类似问题可以通过调试来研究。 
  
 
 如果觉得本文对你有帮助,欢迎点赞,欢迎关注我,如果有补充欢迎评论交流,我将努力创作更多更好的文章。 
 另外欢迎加入我的知识星球,知识星球ID:15165241 一起交流学习。 
 https://t.zsxq.com/Z3bAiea  申请时标注来自CSDN。 
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019/06/02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、背景
  • 二、解析
  • 2.1 官方文档大法
  • 2.2 源码大法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档