关于后端:mybatis-selectKey-赋值未生效为什么

50次阅读

共计 4406 个字符,预计需要花费 12 分钟才能阅读完成。

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 显著是不对的。

对于 LAST_INSERT_ID() 能够参考 LAST_INSERT_ID 文章。

开始探索为什么?

属性值获取到的是 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 注解即可失常运行,

参考资料

https://blog.csdn.net/wt_better/article/details/128851775

https://blog.csdn.net/m0_46205920/article/details/122103024

Mybatis selectKey 采坑笔记

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

正文完
 0