mybatis selectKey赋值未生效的原因分析

 更新时间:2024年02月28日 08:22:29   作者:老马啸西风  
这篇文章主要介绍了mybatis selectKey赋值未生效的原因分析,selectKey 会将 SELECT LAST_INSERT_ID()的结果放入到传入的实体类的主键里面,文中通过代码示例给大家讲解非常详细,需要的朋友可以参考下

Statement配置如下:

 <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 层代码如下

public interface UserMapper{
   int insert(@Param(user) User user);
}
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明显是不对的。

开始探究为什么?

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

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

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,

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字段值,刚好和我们预期的想法一致。

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实现的代码片段:

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

 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注解即可正常运行

以上就是mybatis selectKey赋值未生效的原因分析的详细内容,更多关于mybatis selectKey赋值未生效的资料请关注脚本之家其它相关文章!

相关文章

  • java实现简易的学籍管理系统

    java实现简易的学籍管理系统

    这篇文章主要为大家详细介绍了java实现简易的学籍管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • 如何解决java.lang.NoClassDefFoundError:Could not initialize class java.awt.Color问题

    如何解决java.lang.NoClassDefFoundError:Could not initi

    文章讲述了在Java服务器中处理图形元素时遇到的常见问题,即需要运行X-server,通过在Tomcat/bin/catalina.sh中增加JAVA_OPTS环境变量并设置-Djava.awt.headless=true,可以解决这个问题,使服务器能够在没有图形界面的情况下运行
    2024-11-11
  • Java中的排序与内部比较器Compareable解析

    Java中的排序与内部比较器Compareable解析

    这篇文章主要介绍了Java中的排序与内部比较器Compareable解析,一般没有特殊要求时,直接调用(底层默认的升序排列)就可以得到想要的结果,所谓的 sort 方法排序底层都是基于这两种排序,故如果需要设计成所想要的排序就需要了解底层排序原理,需要的朋友可以参考下
    2023-11-11
  • springboot2.x实现oauth2授权码登陆的方法

    springboot2.x实现oauth2授权码登陆的方法

    这篇文章主要介绍了springboot2.x实现oauth2授权码登陆的方法,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-08-08
  • Java中的悲观锁与乐观锁是什么

    Java中的悲观锁与乐观锁是什么

    这篇文章主要介绍了Java中的悲观锁与乐观锁是什么,帮助大家更好的理解和学习Java锁的相关知识,感兴趣的朋友可以了解下
    2020-09-09
  • JavaEE线程安全实现线程池方法

    JavaEE线程安全实现线程池方法

    这篇文章主要介绍了JavaEE线程安全实现线程池方法,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-06-06
  • Java中Spring的Security使用详解

    Java中Spring的Security使用详解

    这篇文章主要介绍了Java中Spring的Security使用详解,在web应用开发中,安全无疑是十分重要的,选择Spring Security来保护web应用是一个非常好的选择,需要的朋友可以参考下
    2023-07-07
  • Java Autowired注解深入分析

    Java Autowired注解深入分析

    @Autowired注解是Spring中非常重要且常见的,接下来就简要的介绍一下它的用法。@Autowired默认是通过set方法,按照类型自动装配JavaBean,set方法可省略不写,它主要是修饰在成员变量上
    2023-01-01
  • java socket实现局域网聊天

    java socket实现局域网聊天

    这篇文章主要为大家详细介绍了java socket实现局域网聊天,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • IDEA中配置操作Git的详细图文教程

    IDEA中配置操作Git的详细图文教程

    这篇文章给大家详细介绍在IDEA中配置Git,IDEA中操作Git的详细教程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2020-10-10

最新评论