mybatis selectKey 赋值未生效

问题

mybatis 使用中,发现 selectKey 赋值没有生效。

为什么呢?

Statement配置如下:

xml 复制代码
 <insert id="insertStatemnet" parameterType="User">
       insert into user ( user_name )  
       values  (#{user#userName})
  	 <selectKey resultType="long" order="AFTER"
             keyProperty="id">
      	SELECT LAST_INSERT_ID() 
  	</selectKey>
  </insert>

Dao 层代码如下

java 复制代码
public interface UserMapper{
   int insert(@Param(user) User user);
}
java 复制代码
class User{
     long id;
     String userName;

     ...getter/setter
}


User user = userMapper.insert(user);

user.getId() 获取的结果等于0

使用Mybatis Insert User表,使用selectKey获取LAST_INSERT_ID() ,赋值给user的id属性,发现id属性值未被set进去,user.getId() 获取的结果等于0,会话的LAST_INSERT_ID() 已经到远远超过0了,返回0明显是不对的。

关于LAST_INSERT_ID() 可以参考 LAST_INSERT_ID 文章。

开始探究为什么?

属性值获取到的是0,要么是SELECT LAST_INSERT_ID() sql未执行,使用了id long类型的默认值0,要么是执行了但获取到的值是0,或者是Mybatis set对象id属性值的时候没set进去。

Mybatis使用SelectKeyGenerator处理selectKey标签,从这里开始入手

java 复制代码
private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
    try {
      if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
        String[] keyProperties = keyStatement.getKeyProperties();
        final Configuration configuration = ms.getConfiguration();
        final MetaObject metaParam = configuration.newMetaObject(parameter);
        if (keyProperties != null) {
          Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
          List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
          if (values.size() == 0) {
            throw new ExecutorException("SelectKey returned no data.");            
          } else if (values.size() > 1) {
            throw new ExecutorException("SelectKey returned more than one value.");
          } else {
            MetaObject metaResult = configuration.newMetaObject(values.get(0));
            ...
            setValue(metaParam, keyProperties[0], values.get(0));
            ....
          }
        }
      }
    } catch (ExecutorException e) {
      throw e;
    } catch (Exception e) {
      throw new ExecutorException("Error selecting key or setting result to parameter object. Cause: " + e, e);
    }
  }

验证发现keyExecutor.query获取到的values是[100],说明SELECT LAST_INSERT_ID() 查询没问题,那问题必然出现在下面的setValue方法里面。

继续跟进setValue,setValue最终走的是ObjectWrapper的实现类MapWrapper#set,

java 复制代码
class MapWrapper{
	  @Override
	  public void set(PropertyTokenizer prop, Object value) {
	    if (prop.getIndex() != null) {
	      Object collection = resolveCollection(prop, map);
	      setCollectionValue(prop, collection, value);
	    } else {
	      map.put(prop.getName(), value);
	    }
	  }
}

该方法set只是在map里面put了一个kv,理论上Mybatis要对字段赋值的话,应该反射调用对象Filed的set方法。

于此同时看到ObjectWrapper有一个实现类BeanWrapper,其中的set方法是反射set字段值,刚好和我们预期的想法一致。

java 复制代码
class BeanWrapper{
  @Override
  public void set(PropertyTokenizer prop, Object value) {
    if (prop.getIndex() != null) {
      Object collection = resolveCollection(prop, object);
      setCollectionValue(prop, collection, value);
    } else {
      setBeanProperty(prop, object, value);
    }
  }
}

此估计就是决策ObjectWrapper的时候出现问题导致的属性值为set进去,下面是获取ObjectWrapper实现的代码片段:

java 复制代码
private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    this.originalObject = object;
    this.objectFactory = objectFactory;
    this.objectWrapperFactory = objectWrapperFactory;
    this.reflectorFactory = reflectorFactory;

    if (object instanceof ObjectWrapper) {
      this.objectWrapper = (ObjectWrapper) object;
    } else if (objectWrapperFactory.hasWrapperFor(object)) {
      this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
    } else if (object instanceof Map) {
      this.objectWrapper = new MapWrapper(this, (Map) object);
    } else if (object instanceof Collection) {
      this.objectWrapper = new CollectionWrapper(this, (Collection) object);
    } else {
      this.objectWrapper = new BeanWrapper(this, object);
    }
  }

理论上要走到else逻辑的,实际object类型是MapperMethod.ParamMap类型,走到了Map分支。

MapperMethod.ParamMap类型是在MapperMethod执行过程中转换java对象参数到sql命令行参数生成的,具体参考ParamNameResolver#getNamedParams

java 复制代码
 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<String, Object> param = new ParamMap<Object>();
      int i = 0;
      for (Map.Entry<Integer, String> 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;
    }
  }

因为Mapper方法参数里面标注了@Param注解,导致生成的是MapperMethod.ParamMap。

回过头来理下,因为Mapper方法参数里面标注了@Param注解,导致生成的sql参数类型是MapperMethod.ParamMap,继而导致获取MetaObject的时候ObjectWrapper被错误决策成MapWrapper,导致setValue属性值未set进去。

实际上还是使用不规范导致的问题,去掉方法上的@Param注解即可正常运行,

参考资料

blog.csdn.net/wt_better/a...

blog.csdn.net/m0_46205920...

Mybatis selectKey 采坑笔记

本文由博客一文多发平台 OpenWrite 发布!

相关推荐
lllsure几秒前
【Docker】镜像
java·spring cloud·docker
zhysunny3 分钟前
51.不可变基础设施:云原生时代的「乐高城堡」建造法
java·云原生
无名客010 分钟前
SQL语句执行时间太慢,有什么优化措施?以及衍生的相关问题
java·数据库·sql·sql语句优化
风槐啊15 分钟前
邪修实战系列(3)
java·ide·spring boot·spring·tomcat
咋吃都不胖lyh19 分钟前
SQL数据分析原代码--创建表与简单查询
java·数据库·sql
毕设源码-朱学姐21 分钟前
【开题答辩全过程】以 _基于SpringBoot技术的“树洞”心理咨询服务平台的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
boonya29 分钟前
Java内存模型与线程私有共享区域与直接内存的理解
java·开发语言·内存模型
axban40 分钟前
QT M/V架构开发实战:QAbstractItemModel介绍
java·数据库·qt
哈喽姥爷1 小时前
Spring Boot--yml配置信息书写和获取
java·数据库·spring boot·mybatis
LeaderSheepH1 小时前
Java自定义比较器详解
java·开发语言