问题
mybatis 应用中,发现 selectKey 赋值没有失效。
为什么呢?
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 公布!