大家好,我是了不起,前一段時(shí)間在工作中因?yàn)橐粋€(gè)疏忽踩了一個(gè)坑,最終通過(guò)異常棧追溯源碼解決了問(wèn)題。
下面我來(lái)給大家還原一下案發(fā)現(xiàn)場(chǎng),并介紹一下自己的解決思路,希望能對(duì)大家有所啟發(fā)。
當(dāng)時(shí)的業(yè)務(wù)邏輯主要通過(guò) Mybatis 框架來(lái)修改數(shù)據(jù),具體示例如下:
import org.springframework.data.repository.query.Param;public interface GroupMapper { int updateGroup(@Param("oldSerial") Set<Integer> oldSerial, @Param("newSerial") int newSerial); int invalidGroup(@Param("set") Set<Integer> set);}
<update id="updateGroup"> update groupCode_table_use set groupCode=#{newSerial} where groupCode in <foreach collection="oldSerial" close=")" open="(" item="item" separator=","> #{item} </foreach></update><update id="invalidGroup"> update groupCode_table set status='無(wú)效' where groupCode in <foreach collection="set" close=")" open="(" item="item" separator=","> #{item} </foreach></update>
本以為 so easy 的代碼,出現(xiàn)意外了!第一個(gè)sql語(yǔ)句updateGroup正常運(yùn)行,第二個(gè)sql語(yǔ)句invalidGroup竟然報(bào)錯(cuò)了???
報(bào)錯(cuò)信息如下:
圖片
從日志上可以看出,提示set參數(shù)找不到!
明明使用了@Param("set")將參數(shù)命名為set為何找不到,完全不符合多年開(kāi)發(fā)的認(rèn)知。
更加詭異的是updateGroup已使用了同樣的方式去遍歷,完全沒(méi)得問(wèn)題,那么問(wèn)題出現(xiàn)在了哪了?
小伙伴可以先猜一猜!
百思不得其解下,我掏出了祖?zhèn)鹘^活 debug 源碼,最終發(fā)現(xiàn)原來(lái)是Mybatis對(duì)集合Set進(jìn)行了特殊處理。
案發(fā)項(xiàng)目引入的Mybatis版本是 3.5.1。部分源碼如下!
org.apache.ibatis.session.defaults.DefaultSqlSession.java@Overridepublic int update(String statement, Object parameter) { try { dirty = true; MappedStatement ms = configuration.getMappedStatement(statement); //調(diào)用了wrapCollection方法對(duì)參數(shù)進(jìn)行了處理 return executor.update(ms, wrapCollection(parameter)); } catch (Exception e) { throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}/*** 如果參數(shù)實(shí)現(xiàn)了Collection接口或是數(shù)組類型 wrapCollection方法會(huì)對(duì)參數(shù)進(jìn)行封裝* 如果參數(shù)實(shí)現(xiàn)了Collection接口會(huì)封裝為含collection鍵的Map* 如果參數(shù)又實(shí)現(xiàn)了List接口會(huì)封裝為含list鍵的Map(追增)* 對(duì)Set集合沒(méi)有特殊處理* 如果參數(shù)是數(shù)組類型會(huì)封裝為含array鍵的Map*/private Object wrapCollection(final Object object) { if (object instanceof Collection) { StrictMap<Object> map = new StrictMap<>(); map.put("collection", object); if (object instanceof List) { map.put("list", object); } return map; } else if (object != null && object.getClass().isArray()) { StrictMap<Object> map = new StrictMap<>(); map.put("array", object); return map; } return object;}
通過(guò)以上的源碼分析,發(fā)現(xiàn)Mybatis框架對(duì)集合參數(shù)進(jìn)行了特殊處理。
這就是為什么報(bào)錯(cuò)信息中提示Available parameters are [collection]。
找到了collection錯(cuò)誤信息從哪里來(lái)問(wèn)題,接下來(lái)我們分析一下set參數(shù)到了哪里去。
首先updateGroup可以正常執(zhí)行是因?yàn)樵创a中對(duì)集合的特殊處理只對(duì)單參數(shù)生效,也就是說(shuō)@Param("set")注解失效是因?yàn)楸籑ybatis自家的特殊處理給覆蓋了?
這不合乎常理啊,那么問(wèn)題可能出在@Param注解身上,通過(guò)排查代碼發(fā)現(xiàn)GroupMapper.java類引用的@Param注解不對(duì)!
// 代碼引入的注解類Paramimport org.springframework.data.repository.query.Param;// 期望的注解類,應(yīng)該由mybatis提供import org.apache.ibatis.annotations.Param;
實(shí)際上,springframework包下的@Param注解執(zhí)行時(shí)機(jī)在wrapCollection處理之前,wrapCollection對(duì)集合的特殊處理將springframework包下的@Param注解處理覆蓋掉了,所以無(wú)法解析參數(shù)set。而mybatis包下的@Param注解執(zhí)行時(shí)機(jī)在wrapCollection處理之后,程序可以正常運(yùn)行。
最終確定,繞了這么大一圈,原來(lái)是包導(dǎo)入錯(cuò)了,誰(shuí)能想到springframework包下還有一個(gè)@Param注解,在多參數(shù)的情況下竟然可以正常使用o(╥﹏╥)o。
整個(gè)異常排查過(guò)程中,主要通過(guò)分析異常棧信息進(jìn)行快速定位。下面我給大家介紹一下異常棧分析法。
我們?cè)诠ぷ髦胁榭串惓#际菢I(yè)務(wù)代碼異常導(dǎo)致的,這個(gè)時(shí)候我們只需要關(guān)心是在我們編寫(xiě)的哪段代碼中出了問(wèn)題。
而這時(shí)好用的idea也會(huì)為溫馨的為我們做出提示,會(huì)在異常棧中將我們編寫(xiě)的方法位置信息標(biāo)藍(lán)處理,這樣我們就定快速定位出現(xiàn)問(wèn)題的代碼。
圖片
但是一旦是底層引用的jar包出現(xiàn)了異常,僅僅是這樣查看異常棧是不夠的。下面我們就以上面案例中的異常棧來(lái)帶大家分析。
當(dāng)前異常:
圖片
原始異常:
圖片
分析流程:
"Caused by" 是 Java 異常處理機(jī)制中的一部分,它表示當(dāng)前異常是由另一個(gè)異常引起的。在 Java 中,每個(gè) Throwable 對(duì)象都可以通過(guò) getCause() 方法獲取到原始異常,這個(gè)原始異常就是通過(guò) "Caused by" 打印出來(lái)的。
假設(shè)你在catch塊中捕獲了一個(gè)異常,并重新拋出了一個(gè)新的異常,同時(shí)保留了原始異常的信息:
public class Example { public static void main(String[] args) { try { methodThatThrowsException(); } catch (Exception e) { throw new RuntimeException("Caught an exception", e); } } public static void methodThatThrowsException() throws Exception { throw new Exception("Original exception"); }}
當(dāng)你運(yùn)行上述代碼時(shí),控制臺(tái)輸出的異常棧信息通常會(huì)包含Caused by信息:
java.lang.RuntimeException: Caught an exception at Example.main(Example.java:7)Caused by: java.lang.Exception: Original exception at Example.methodThatThrowsException(Example.java:12) at Example.main(Example.java:5)
如何保留原始異常信息呢?
try { methodThatThrowsException(); } catch (Exception e) { throw new RuntimeException("Caught an exception", e); }
try { methodThatThrowsException(); } catch (Exception e) { RuntimeException newException = new RuntimeException("Caught an exception"); newException.addSuppressed(e); throw newException; }
學(xué)會(huì)查看分析異常棧,可以為我們工作大大提高效率,希望這篇文章給大家?guī)?lái)收獲,最后再送給大家一個(gè)小技巧。異常棧不僅僅可以用來(lái)排查異常哦,還可以幫助大家學(xué)習(xí)源碼,debug 源碼找不到入口怎么辦,那就創(chuàng)造一個(gè)異常!
本文鏈接:http://www.www897cc.com/showinfo-26-112730-0.html盤(pán)點(diǎn) Mybatis 使用過(guò)程中遇到的坑!
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com