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

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

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

1、背景

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

更新開票請求返回日志, 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

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

2、報警原因定位

首先是具體的報警原因:

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

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

1.查看了報警的Mapper方法,如下代碼所示, 這個是接收返回參數,根據主鍵id,更新具體響應內容和時間的代碼,入參有3個,類型分別為long, String 和 LocalDateTime

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

2.查看了Mapper方法對應的XML文件,如下代碼,對應的parameterType類型是String,而實際參數的類型有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上線前后的版本,因為報警的內容是Mybatis處理sql語句時,發(fā)現不能將LocalDateTime轉型為String,這一段邏輯在上線前是ok的,上線的業(yè)務邏輯對這段歷史代碼無改動,因此猜測是統(tǒng)一pom的升級,導致Mybatis的版本發(fā)生了變化,某些歷史功能不支持了。 mybatis版本上線前后的變化,1.3.9.6對應的版本是3.2.3,1.4.2.1對應的版本是3.4.6。

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

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

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

報警原因已定位,但為什么版本升級后就不兼容歷史的用法,并且具體不兼容的是哪一塊內容,背后的原理又是什么,請看接下來章節(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在構建sql語句時,發(fā)現時間字段 類型為LocalDateTime 不能強制轉為String類型。 這個SQL XML的配置在3.2.3的版本是正??梢杂茫敲词紫仁菑腗ybatis 的 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.

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

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

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

首先,先看以下配置,定義了一個通過主鍵id獲取學生信息的方法,仿造系統(tǒng)內的歷史代碼,也將parameterType定義為 java.lang.String 和 方法對應的參數 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框架要做的事情就是在運行getStudentById(2)的時候,將 #{id}進行替換,使SQL語句變成 SELECT id,name,age FROM student WHERE id = 2 。Mybatis要將SQL語句完整替換成帶參數值的版本,需要經歷框架初始化以及實際運行時動態(tài)替換兩個部分。因為Mybatis的代碼非常多,接下來主要闡釋和本次案例相關的內容。

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

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

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

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

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

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

在框架初始化階段,需要介紹的內容,在3.2.3版本已經介紹完畢,接下來是當執(zhí)行getStudentById方法時,Mybatis的流程,如下圖所示,受限于圖片長度,進行了布局的調整:

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

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

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

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

StatementHandler 封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設置參數、將Statement結果集轉換成List集合。

ParameterHandler 負責對用戶傳遞的參數轉換成JDBC Statement 所需要的參數

TypeHandler 負責java數據類型和jdbc數據類型之間的映射和轉換

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

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

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

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

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

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

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

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

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

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


后續(xù)就是執(zhí)行SQL并處理返回結果,不在本文的討論范圍內,從上文的分析中,我們可以了解到,在3.2.3及以下版本,Mybatis會忽略parmeterType,在真正進行sql轉換時,重新根據sql方法入參類型計算合適的TypeHandler處理器,所以本案例中的代碼在3.2.3時運行時正常的。

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

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

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

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

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

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

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

4、總結

總結一下這個案例的主要原因是:

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

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

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

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

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

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

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

相關文章

最新評論