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() 獲取的結(jié)果等于0
使用Mybatis Insert User表,使用selectKey獲取LAST_INSERT_ID() ,賦值給user的id屬性,發(fā)現(xiàn)id屬性值未被set進(jìn)去,user.getId() 獲取的結(jié)果等于0,會(huì)話的LAST_INSERT_ID() 已經(jīng)到遠(yuǎn)遠(yuǎn)超過0了,返回0明顯是不對(duì)的。
開始探究為什么?
屬性值獲取到的是0,要么是SELECT LAST_INSERT_ID() sql未執(zhí)行,使用了id long類型的默認(rèn)值0,要么是執(zhí)行了但獲取到的值是0,或者是Mybatis set對(duì)象id屬性值的時(shí)候沒set進(jìn)去。
Mybatis使用SelectKeyGenerator處理selectKey標(biāo)簽,從這里開始入手
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);
}
}
驗(yàn)證發(fā)現(xiàn)keyExecutor.query獲取到的values是[100],說明SELECT LAST_INSERT_ID() 查詢沒問題,那問題必然出現(xiàn)在下面的setValue方法里面。
繼續(xù)跟進(jìn)setValue,setValue最終走的是ObjectWrapper的實(shí)現(xiàn)類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了一個(gè)kv,理論上Mybatis要對(duì)字段賦值的話,應(yīng)該反射調(diào)用對(duì)象Filed的set方法。
于此同時(shí)看到ObjectWrapper有一個(gè)實(shí)現(xiàn)類BeanWrapper,其中的set方法是反射set字段值,剛好和我們預(yù)期的想法一致。
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);
}
}
}
此估計(jì)就是決策ObjectWrapper的時(shí)候出現(xiàn)問題導(dǎo)致的屬性值為set進(jìn)去,下面是獲取ObjectWrapper實(shí)現(xiàn)的代碼片段:
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邏輯的,實(shí)際object類型是MapperMethod.ParamMap類型,走到了Map分支。
MapperMethod.ParamMap類型是在MapperMethod執(zhí)行過程中轉(zhuǎn)換java對(duì)象參數(shù)到sql命令行參數(shù)生成的,具體參考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;
}
}
因?yàn)镸apper方法參數(shù)里面標(biāo)注了@Param注解,導(dǎo)致生成的是MapperMethod.ParamMap。
回過頭來理下,因?yàn)镸apper方法參數(shù)里面標(biāo)注了@Param注解,導(dǎo)致生成的sql參數(shù)類型是MapperMethod.ParamMap,繼而導(dǎo)致獲取MetaObject的時(shí)候ObjectWrapper被錯(cuò)誤決策成MapWrapper,導(dǎo)致setValue屬性值未set進(jìn)去。
實(shí)際上還是使用不規(guī)范導(dǎo)致的問題,去掉方法上的@Param注解即可正常運(yùn)行
以上就是mybatis selectKey賦值未生效的原因分析的詳細(xì)內(nèi)容,更多關(guān)于mybatis selectKey賦值未生效的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- MyBatis insert語句返回主鍵和selectKey標(biāo)簽方式
- Mybatis3中方法返回生成的主鍵:XML,@SelectKey,@Options詳解
- mybatis?獲取更新(update)記錄的id之<selectKey>用法說明
- mybatis的selectKey作用詳解
- Mybatis?selectKey 如何返回新增用戶的id值
- MyBatis如何使用selectKey返回主鍵的值
- Mybatis插入時(shí)返回自增主鍵方式(selectKey和useGeneratedKeys)
- Mybatis @SelectKey用法解讀
- Mybatis示例之SelectKey的應(yīng)用
- MyBatis中selectKey標(biāo)簽及主鍵回填實(shí)現(xiàn)
相關(guān)文章
java實(shí)現(xiàn)簡(jiǎn)易的學(xué)籍管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)易的學(xué)籍管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02
如何解決java.lang.NoClassDefFoundError:Could not initi
文章講述了在Java服務(wù)器中處理圖形元素時(shí)遇到的常見問題,即需要運(yùn)行X-server,通過在Tomcat/bin/catalina.sh中增加JAVA_OPTS環(huán)境變量并設(shè)置-Djava.awt.headless=true,可以解決這個(gè)問題,使服務(wù)器能夠在沒有圖形界面的情況下運(yùn)行2024-11-11
Java中的排序與內(nèi)部比較器Compareable解析
這篇文章主要介紹了Java中的排序與內(nèi)部比較器Compareable解析,一般沒有特殊要求時(shí),直接調(diào)用(底層默認(rèn)的升序排列)就可以得到想要的結(jié)果,所謂的 sort 方法排序底層都是基于這兩種排序,故如果需要設(shè)計(jì)成所想要的排序就需要了解底層排序原理,需要的朋友可以參考下2023-11-11
springboot2.x實(shí)現(xiàn)oauth2授權(quán)碼登陸的方法
這篇文章主要介紹了springboot2.x實(shí)現(xiàn)oauth2授權(quán)碼登陸的方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08
java?socket實(shí)現(xiàn)局域網(wǎng)聊天
這篇文章主要為大家詳細(xì)介紹了java?socket實(shí)現(xiàn)局域網(wǎng)聊天,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05

