亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

淺談Mybatis版本升級踩坑及背后原理分析

 更新時間:2020年05月17日 10:42:02   作者:程序員小岑成長記  
這篇文章主要介紹了淺談Mybatis版本升級踩坑及背后原理分析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

1、背景

某一天的晚上,系統(tǒng)服務(wù)正在進行常規(guī)需求的上線,因為發(fā)布時,提示統(tǒng)一的pom版本需要升級,于是從 1.3.9.6 升級至 1.4.2.1。
當(dāng)服務(wù)開始上線后,開始陸續(xù)出現(xiàn)了一些更新系統(tǒng)交互日志方面的報警,屬于系統(tǒng)輔助流程,報警下圖所示, 具體系統(tǒng)數(shù)據(jù)已脫敏,內(nèi)容是Mybatis相關(guān)的報警,在進行類型轉(zhuǎn)換的時候,產(chǎn)生了強轉(zhuǎn)錯誤。

更新開票請求返回日志, id:{#######}, response:{{"code":XXX,"data":{"callType":3,"code":XXX,"msg":"XXXX","shopId":XXXXX,"taxPlateDockType":"XXXXXXX"},"msg":"XXXXX","success":XXXX}}
nested execption is org.apache.ibatis.type.TypeException: Could not set parameters for mapping: ParameterMapping{property='updateTime', mode=IN, javaType=class java.lang.String,
jdbcTyp=null,resultMapId='null',jdbcTypeName='null',expression='null'}.Cause org.apache.ibatis.type.TypeException,Error setting non null parameter #2 with JdbcType null. Try setting a
different Jdbc Type for this parameter or a different configuration property.Cause java.lang.ClassCastException:java.time.LocalDateTime cannot be cast to java.lang.String

報警的一塊代碼,屬于歷史功能,失敗并不會影響主流程,但在定位期間,會頻繁報警,造成一定的干擾,因此當(dāng)時首先采取回滾操作,將統(tǒng)一的pom版本回滾至歷史版本,報警消失,再進行問題的定位和分析。
以下章節(jié)是對報警原因的定位及原因詳細分析的介紹。

2、報警原因定位

首先是具體的報警原因:

由于mybatis版本由inf-bom引入而來,在inf-bom升級后,由3.2.3 升級至了 3.4.6版本,而Mybatis自3.2.4開始就不支持目前系統(tǒng)內(nèi)的SQL Mapper的用法,因此上線后,線上出現(xiàn)頻繁報警。接下來是定位的過程。

回滾完畢后,開始具體分析報警產(chǎn)生的主要原因,進行了以下幾步的排查。

1.查看了報警的Mapper方法,如下代碼所示, 這個是接收返回參數(shù),根據(jù)主鍵id,更新具體響應(yīng)內(nèi)容和時間的代碼,入?yún)⒂?個,類型分別為long, String 和 LocalDateTime

int updateResponse(@Param("id")long id, @Param("response")String response, @Param("updateTime")LocalDateTime updateTime);

2.查看了Mapper方法對應(yīng)的XML文件,如下代碼,對應(yīng)的parameterType類型是String,而實際參數(shù)的類型有Long,有String,也有LocalDateTime。

<update id="updateResponse" parameterType="java.lang.String">
UPDATE invoice_log
 SET response = #{response}, update_time = #{updateTime}
WHERE id = #{id}
</update>

3.查看了Mybatis上線前后的版本,因為報警的內(nèi)容是Mybatis處理sql語句時,發(fā)現(xiàn)不能將LocalDateTime轉(zhuǎn)型為String,這一段邏輯在上線前是ok的,上線的業(yè)務(wù)邏輯對這段歷史代碼無改動,因此猜測是統(tǒng)一pom的升級,導(dǎo)致Mybatis的版本發(fā)生了變化,某些歷史功能不支持了。 mybatis版本上線前后的變化,1.3.9.6對應(yīng)的版本是3.2.3,1.4.2.1對應(yīng)的版本是3.4.6。

4.通過第3步可以得到,在這次inf-bom的版本升級中,mybatis3的版本直接升了兩個大版本,因此可以基本將原因猜測為 Mybatis升級跨度大,導(dǎo)致部分歷史功能沒有兼容支持,引起的線上sql更新報錯。

5.為了具體驗證第4步的想法,通過UT的方式,通過將Mybatis的版本不斷從3.4.6往下降,直至沒有報錯位置,最終定位是Mybatis版本為3.2.3時,線上代碼是正??捎玫?,只要升一個版本也就是自3.2.4開始,就開始不兼容目前的用法。(這個當(dāng)時思路不是很好,應(yīng)該從小版本逐個往上升,可以去加速定位版本的效率)

最后定位報警原因,由于mybatis版本由統(tǒng)一pom引入而來,在統(tǒng)一pom升級后,由3.2.3 升級至了 3.4.6版本,而Mybatis自3.2.4開始就不支持目前系統(tǒng)內(nèi)的SQL Mapper的用法,因此上線后,線上出現(xiàn)頻繁報警。

報警原因已定位,但為什么版本升級后就不兼容歷史的用法,并且具體不兼容的是哪一塊內(nèi)容,背后的原理又是什么,請看接下來章節(jié)的詳細分析。

3、詳細分析

3.1 Mybatis 升級3.2.4版本的官方Release公告

首先從報錯的原因上來看,Caused by: java.lang.ClassCastException: java.lang.LocalDateTime cannot be cast to java.lang.String ,是Mybatis在構(gòu)建sql語句時,發(fā)現(xiàn)時間字段 類型為LocalDateTime 不能強制轉(zhuǎn)為String類型。 這個SQL XML的配置在3.2.3的版本是正??梢杂?,那么首先是從Mybatis 的 release log上查看3.2.4版本 發(fā)生了什么變化。

An special remark about this feature. Previous versions ignored the "parameterType" attribute and used the actual parameter to calculate bindings. This version builds the binding information during startup and the "parameterType" attribute is used if present (though it is still optional), so in case you had a wrong value for it you will have to change it.

從官網(wǎng)的Release Log可以看出,Mybatis在3.2.4以前的版本,是忽略XML中的parameterType這個屬性,并且使用真實的變量類型進行值的處理,在3.2.4及以后的版本中,這個屬性會被啟用,因此如果出現(xiàn)類型不匹配的話,就會出現(xiàn)轉(zhuǎn)型失敗的報錯,也提示我們開發(fā)者在升級到這個版本及以上時,需要檢查系統(tǒng)內(nèi)的XML配置,使類型相匹配,或者不設(shè)置該屬性,讓Mybatis自行進行計算。

從以上內(nèi)容,可以了解到,在版本升級后,mybatis在構(gòu)建sql語句,獲取字段值的時候邏輯發(fā)生了變化,那么接下來通過一個普通的示例,了解mybatis在獲取字段值這一塊的具體代碼流程是怎樣的,以3.2.3版本為例。

3.2 以版本3.2.3為例,mybatis構(gòu)建SQL語句過程的原理分析

首先,先看以下配置,定義了一個通過主鍵id獲取學(xué)生信息的方法,仿造系統(tǒng)內(nèi)的歷史代碼,也將parameterType定義為 java.lang.String 和 方法對應(yīng)的參數(shù) int 并不相同。

public StudentEntity getStudentById(@Param("id") int id);

<select id="getStudentById" parameterType="java.lang.String" resultType="entity.StudentEntity">
SELECT id,name,age FROM student WHERE id = #{id}
</select>

mybatis框架要做的事情就是在運行g(shù)etStudentById(2)的時候,將 #{id}進行替換,使SQL語句變成 SELECT id,name,age FROM student WHERE id = 2 。Mybatis要將SQL語句完整替換成帶參數(shù)值的版本,需要經(jīng)歷框架初始化以及實際運行時動態(tài)替換兩個部分。因為Mybatis的代碼非常多,接下來主要闡釋和本次案例相關(guān)的內(nèi)容。

在框架初始化階段,主要有以下流程,如下圖所示

在框架初始化階段,有一些組件會被構(gòu)建,接下來進行逐一做個簡單的介紹:

  • SqlSession 作為MyBatis工作的主要頂層API,表示和數(shù)據(jù)庫交互的會話,完成必要數(shù)據(jù)庫增刪改查功能。
  • SqlSource 負責(zé)根據(jù)用戶傳遞的parameterObject,動態(tài)地生成SQL語句,將信息封裝到BoundSql對象中,并返回。
  • Configuration MyBatis所有的配置信息都維持在Configuration對象之中。

接下來主要關(guān)注SqlSource,這個類會負責(zé)在負責(zé)生成SQL語句,也是本次案例中,3.2.3和3.2.4差異比較大的地方。接下來會一些源碼部分的介紹。

在構(gòu)建Configuration的過程中,會涉及到構(gòu)建對應(yīng)每一條sql語句對應(yīng)的MappedStatemnt,在parmeterTypeClass就是根據(jù)我們在xml配置中寫的parmeterType轉(zhuǎn)換而來,值為java.lang.String,在接下來構(gòu)建SqlSource中,傳入了這個參數(shù),如下圖所示:

在SqlSource的構(gòu)建階段中,parameterType參數(shù)其實是被忽略不使用的,這也和官方的描述是一致的,3.2.4之前這個parameterType屬性是被忽略的,然后創(chuàng)建了DynamicSqlSource,這個類主要是用于處理Mybatis動態(tài)Sql的類。

在框架初始化階段,需要介紹的內(nèi)容,在3.2.3版本已經(jīng)介紹完畢,接下來是當(dāng)執(zhí)行g(shù)etStudentById方法時,Mybatis的流程,如下圖所示,受限于圖片長度,進行了布局的調(diào)整:

在具體執(zhí)行階段,也有一些組件,我們需要做了解

SqlSession 作為MyBatis工作的主要頂層API,表示和數(shù)據(jù)庫交互的會話,完成必要數(shù)據(jù)庫增刪改查功能

Executor MyBatis執(zhí)行器,是MyBatis 調(diào)度的核心,負責(zé)SQL語句的生成和查詢緩存的維護

BoundSql 表示動態(tài)生成的SQL語句以及相應(yīng)的參數(shù)信息

StatementHandler 封裝了JDBC Statement操作,負責(zé)對JDBC statement 的操作,如設(shè)置參數(shù)、將Statement結(jié)果集轉(zhuǎn)換成List集合。

ParameterHandler 負責(zé)對用戶傳遞的參數(shù)轉(zhuǎn)換成JDBC Statement 所需要的參數(shù)

TypeHandler 負責(zé)java數(shù)據(jù)類型和jdbc數(shù)據(jù)類型之間的映射和轉(zhuǎn)換

接下來主要關(guān)注在獲取BoundSql以及參數(shù)化語句的流程,也是本次案例中,3.2.3和3.2.4差異比較大的地方。接下來會一些源碼部分的介紹。

在進入Executor的query方法后,會首先通過對應(yīng)的MappedStatement獲取BoundSql,用來幫助我們動態(tài)生成SQL語句,里面綁定了對應(yīng)的SQL以及參數(shù)映射關(guān)系,在構(gòu)建框架階段,我們使用的SqlSource是DynamicSqlSource,通過這個類來生成獲取BoundSql。

通過上圖的代碼可以得知,parameterType在初始化階段未被使用,而是在SQL執(zhí)行時,獲取到的,但獲取到的類型是parameterObject對應(yīng)的類型,這個類是用來記錄mapper方法上對應(yīng)的參數(shù)的。如下圖所示,并非在Sql配置文件中標(biāo)注的java.lang.String。

接下來,通過SqlSourceBuilder sqlSourceParser 對sql以及計算得到的類型進行再次處理,當(dāng)中流程代碼比較長,主要是在這個過程中去制作 sql方法的入?yún)?和 java類型的綁定關(guān)系,mybatis依賴這個綁定關(guān)系使用對應(yīng)的TypeHandler去進行值的轉(zhuǎn)換,調(diào)用鏈路是SqlSourceParser.parse -> 內(nèi)部類 ParameterMappingTokenHandler.handleToken -> 私有方法 buildParameterMapping, 如下圖代碼所示。因為當(dāng)前的parmeterType為 MapperMethod$ParamMap,進過了多個if判斷,判定當(dāng)前property id 的 propertyType 為Object.class類型,接下來就是制作 sql方法的入?yún)?和 java類型的綁定關(guān)系 parameterMapping,并進行了返回。

制作完成的ParameterMapping的結(jié)構(gòu)如下圖代碼所示,參數(shù)id對應(yīng)的javaType類型為 java.lang.Object,對應(yīng)的TypeHander處理器為UnknownTypeHandler,也就是未找到合適的TypeHandler的兜底選項。

接下來流程就會流轉(zhuǎn)到Executor, org.apache.ibatis.executor.SimpleExecutor#doQuery進行查詢時,會根據(jù)當(dāng)前的SQL類型,生成對應(yīng)的statmentHandler,因為我們目前都是用的預(yù)編譯SQL,因此生成的statementHandler就是PrepareStatmentHandler,熟悉JDBC的小伙伴應(yīng)該馬上可以猜到這對應(yīng)的語句是什么類型了。接下來就會對這句SQL語句進行填充,如下圖代碼所示,會通過PrepareStatmentHandler的parameterize方法對Statment進行參數(shù)化,也就是進行填充過程。

在PreparseStatmentHandler進行參數(shù)化時,會將參數(shù)化的職責(zé)交給DefaultParameterHandler進行,如下圖代碼所示,主要關(guān)注紅線部分,首先會獲取parameterMapping對應(yīng)的TypeHander,如上章節(jié)所示,獲取到的是UnknownTypeHandler,然后會通過setParameter方法,將參數(shù)id替換成對應(yīng)的值。

在typehandler的流程里,首先會進入BaseTypeHandler,然后在具體設(shè)置時,進入子類的方法,在UnknownTypeHandler,首先會再次對parameter進行解析,判斷最正確的TypeHandler類型,如下圖代碼所示:

在resolveTypeHandler方法中,因為已知參數(shù)值的類型,通過Integer這個class在typeHandlerRegistry中尋找對應(yīng)的TypeHandler,TypeHandlerRegistry是Mybatis啟動時內(nèi)置好的,java對象類型和TypeHandler的映射關(guān)系,有興趣的可以進這個類詳細看下,在本案例中,會直接獲取到IntegerHandler,如下圖代碼所示:

在獲取到IntegerHandler后,就可以使用IntegerTypeHandler的setInt方法,對SQL語句中的參數(shù)進行替換,如下圖代碼所示,sql語句被成功替換。


后續(xù)就是執(zhí)行SQL并處理返回結(jié)果,不在本文的討論范圍內(nèi),從上文的分析中,我們可以了解到,在3.2.3及以下版本,Mybatis會忽略parmeterType,在真正進行sql轉(zhuǎn)換時,重新根據(jù)sql方法入?yún)㈩愋陀嬎愫线m的TypeHandler處理器,所以本案例中的代碼在3.2.3時運行時正常的。

3.3 以版本3.2.4為例,相比版本3.2.3,mybatis構(gòu)建SQL語句過程的變化分析

在3.2章節(jié)中,得知mybatis是在運行sql階段重新計算參數(shù)對應(yīng)的TypeHandler進行sql參數(shù)替換,那么在版本3.2.4中,mybatis做了什么改動,導(dǎo)致了原有的使用方式不可用了呢。從官方的release log來看,版本3.2.4做了這樣一個改動。

This version builds the binding information during startup and the "parameterType" attribute is used

意思是說 parameterType會在框架運行階段就被使用到,從這個中,我們將分析的重點放在構(gòu)建階段,同時負責(zé)處理綁定關(guān)系的BoundSql由配置階段的SqlSource生成,因此主要查看SqlSource的構(gòu)建,3.2.4發(fā)生了什么變化,如下圖所示。與3.2.3不同,3.2.4首先判斷了是否為動態(tài)SQL,在非動態(tài)SQL情況下,將parameterType java.lang.String作為參數(shù),傳入了SqlSource的構(gòu)造方法。

后續(xù)流程與3.2.3一致,因為parameter類型為java.lang.String,在構(gòu)建parameterMapping時,使用的類型就是java.lang.String。

因為在框架初始化階段,SqlSource中 parameterMapping, id對應(yīng)的類型就是java.lang.String,導(dǎo)致在進行Sql語句替換時,獲取到的TypeHandler是StringTypeHandler,如下圖所示:

后面的報錯原因就比較好理解了,在調(diào)用StringTypeHandler的setString方法時,報出了java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String的錯誤。

4、總結(jié)

總結(jié)一下這個案例的主要原因是:

mybatis 3.2.3版本 兼容parameterType和實際參數(shù)類型不匹配,運行時動態(tài)計算值處理器類型,在大版本升級2個版本號后,parameterType開始生效,以parameterType作為參數(shù)的實際類型進行TypeHandler的獲取計算,導(dǎo)致類型不匹配時,強轉(zhuǎn)報錯。

帶給我自己的在后續(xù)編寫編寫代碼及系統(tǒng)上線方面的啟示是:

1.在統(tǒng)一pom升級時,需要線下進行全面回歸,避免框架存在不兼容的用法,導(dǎo)致線上錯誤。

2.開發(fā)同學(xué)可以檢查自己系統(tǒng)內(nèi)的mybatis版本,如果是3.2.4以下,需要全面檢查下現(xiàn)在的mapper文件里 對于parameterType的使用 和實際的參數(shù)類型是否一致,避免升級到3.2.4及以上版本時發(fā)生兼容報錯,如果有不匹配的情況存在,需要進行修正 或者 不使用parameterType,讓Mybatis在運行SQL時自動計算對應(yīng)的類型,

3.可以考慮使用mybatis-generator來自動生成xml和mapper文件,有專業(yè)團隊維護,相對來說穩(wěn)定性更好,也避免自己手動修改xml文件容易帶來誤操作。

4.可以主動關(guān)注強依賴的一些開源框架的Release log,有很多重要的信息。

到此這篇關(guān)于淺談Mybatis版本升級踩坑及背后原理分析的文章就介紹到這了,更多相關(guān)Mybatis版本升級內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論