Mybatis使用useGeneratedKeys获取自增主键的方法
摘要
我们经常使用useGenerateKeys来返回自增主键,避免多一次查询。也会经常使用onduplicatekeyupdate,来进行insertOrUpdate,来避免先query在insert/update。用起来很爽,但是经常踩坑,还不知为何。本篇就是深入分析获取自增主键的原理。
问题
首先摘两段我司一些老代码的bug
批量插入用户收藏
for(tries=0;triescollectionMapper.insertCollections方法
INSERTINTOcollection( userid,item ) VALUES (#{collection.userId},#{collection.item}) ONDUPLICATEKEYUPDATE status=0 不知道大家能不能发现其中的问题
分析
问题有两个
返回值result的判断错误
使用onduplicatekey批量update返回影响的行数是和插入的数不一样的。犯这种错主要在于想当然,不看文档
看下官网文档
写的很清楚
WithONDUPLICATEKEYUPDATE,theaffected-rowsvalueperrowis1iftherowisinsertedasanewrow,2ifanexistingrowisupdated,and0ifanexistingrowissettoitscurrentvalues.IfyouspecifytheCLIENT_FOUND_ROWSflagtothemysql_real_connect()CAPIfunctionwhenconnectingtomysqld,theaffected-rowsvalueis1(not0)ifanexistingrowissettoitscurrentvalues.
返回值有三种
0:没有更新1:insert2.update
还有一个特殊情况,update一个相同值到原来的值,这个根据客户端配置,可能为0,可能为1。
所以这个判断明显错误
利用批量InsertOrUpdate的userGeneratedKey来返回自增主键
这个问题批量插入时有update语句时,就会发现有问题。返回的自增主键都是错的,这是为什么呢?
1.首先我们看下mybatis对于useGeneratedKey的描述
>ThistellsMyBatistousetheJDBCgetGeneratedKeysmethodtoretrievekeysgeneratedinternallybythedatabase(e.g.autoincrementfieldsinRDBMSlikeMySQLorSQLServer).Default:false.
就是使用JDBC的getGeneratedKeys的方法来获取的。
2.我们再找下JDBC的规范
Beforeversion3.0oftheJDBCAPI,therewasnostandardwayofretrievingkeyvaluesfromdatabasesthatsupportedautoincrementoridentitycolumns.WitholderJDBCdriversforMySQL,youcouldalwaysuseaMySQL-specificmethodontheStatementinterface,orissuethequerySELECTLAST_INSERT_ID()afterissuinganINSERTtoatablethathadanAUTO_INCREMENTkey.UsingtheMySQL-specificmethodcallisn'tportable,andissuingaSELECTtogettheAUTO_INCREMENTkey'svaluerequiresanotherround-triptothedatabase,whichisn'tasefficientaspossible.ThefollowingcodesnippetsdemonstratethethreedifferentwaystoretrieveAUTO_INCREMENTvalues.First,wedemonstratetheuseofthenewJDBC3.0methodgetGeneratedKeys()whichisnowthepreferredmethodtouseifyouneedtoretrieveAUTO_INCREMENTkeysandhaveaccesstoJDBC3.0.ThesecondexampleshowshowyoucanretrievethesamevalueusingastandardSELECTLAST_INSERT_ID()query.ThefinalexampleshowshowupdatableresultsetscanretrievetheAUTO_INCREMENTvaluewhenusingtheinsertRow()method.
意思就是JDBC3.0以前,有些乱七八糟的定义的,没有统一,之后统一成了getGeneratedKeys()方法。两边是一致的。实现的原理主要就是数据库端返回一个LAST_INSERT_ID。这个跟auto_increment_id强相关。
我们看下auto_increment_id的定义。重点关注批量插入
Foramultiple-rowinsert,LAST_INSERT_ID()andmysql_insert_id()actuallyreturntheAUTO_INCREMENTkeyfromthefirstoftheinsertedrows.Thisenablesmultiple-rowinsertstobereproducedcorrectlyonotherserversinareplicationsetup.
批量插入的时候只会返回一个id,这个id值是第一个插入行的AUTO_INCREMENT值。至于为什么这么干,能够使得mysql-server在master-slave架构下也能保证id值统一的原因可以看下这篇。本篇文章就不展开了。
那么mysqlserver只返回一个id,客户端批量插入的时候怎么能实现获取全部的id呢
3.客户端的实现
我们看下客户端getGeneratedKeys的实现。
JDBCcom.mysql.jdbc.StatementImpl
publicsynchronizedResultSetgetGeneratedKeys()throwsSQLException{ if(!this.retrieveGeneratedKeys){ throwSQLError.createSQLException(Messages.getString("Statement.GeneratedKeysNotRequested"),"S1009",this.getExceptionInterceptor()); }elseif(this.batchedGeneratedKeys==null){ //批量走这边的逻辑 returnthis.lastQueryIsOnDupKeyUpdate?this.getGeneratedKeysInternal(1):this.getGeneratedKeysInternal(); }else{ Field[]fields=newField[]{newField("","GENERATED_KEY",-5,17)}; fields[0].setConnection(this.connection); returnResultSetImpl.getInstance(this.currentCatalog,fields,newRowDataStatic(this.batchedGeneratedKeys),this.connection,this,false); } }看下调用的方法this.getGeneratedKeysInternal()
protectedResultSetgetGeneratedKeysInternal()throwsSQLException{ //获取影响的行数 intnumKeys=this.getUpdateCount(); returnthis.getGeneratedKeysInternal(numKeys); }这里有个重要知识点了,首先获取本次批量插入的影响行数,然后再执行具体的获取id操作。
getGeneratedKeysInternal方法
protectedsynchronizedResultSetgetGeneratedKeysInternal(intnumKeys)throwsSQLException{ Field[]fields=newField[]{newField("","GENERATED_KEY",-5,17)}; fields[0].setConnection(this.connection); fields[0].setUseOldNameMetadata(true); ArrayListrowSet=newArrayList(); longbeginAt=this.getLastInsertID(); //按照受影响的范围+递增步长 for(inti=0;i0L){ //值塞进去 row[0]=StringUtils.getBytes(Long.toString(beginAt)); } beginAt+=(long)this.connection.getAutoIncrementIncrement(); } } 迭代影响的行数,然后依次获取id。
所以批量insert是正确可以返回的。
但是批量insertOrUpdate就有问题了,批量insertOrUpdate的影响行数不是插入的数据行数,可能是0,1,2这样就导致了自增id有问题了。
比如插入3条数据,2条会update,1条会insert,这时候updateCount就是5,generateid就会5个了,mybatis然后取前3个塞到数据里,显然是错的。
以上是原理分析,如果想了解更详细的实验结果,可以看下实验
总结
批量insert
insertintoAuthor(username,password,email,bio)values (#{item.username},#{item.password},#{item.email},#{item.bio}) 来自官网的例子,mapper中不能指定@Param参数,否则会有问题
批量insertOrUpdate
不能依赖useGeneratedKey返回主键。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。