新聞中心
本文轉(zhuǎn)載自微信公眾號「 yes的練級攻略」,作者 是Yes呀。轉(zhuǎn)載本文請聯(lián)系 yes的練級攻略公眾號。

成都創(chuàng)新互聯(lián)公司主要從事網(wǎng)站設(shè)計制作、做網(wǎng)站、網(wǎng)頁設(shè)計、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務。立足成都服務魏縣,十多年網(wǎng)站建設(shè)經(jīng)驗,價格優(yōu)惠、服務專業(yè),歡迎來電咨詢建站服務:028-86922220
你好,我是yes。
昨天測試說有個 xx 功能用不了,扔給我一個截圖,說有報錯:
報錯信息就是:Transaction rolled back because it has been marked as rollback-only,很好理解:事務被回滾了,因為它已經(jīng)被標記了只能回滾。
我一看巧了,這不就是我之前分析過的面試題嗎!
之前的文章我解釋過:這種錯一般發(fā)生在嵌套事務中,即內(nèi)層事務出錯,但是由于是否提交事務的操作由外層事務觸發(fā),于是乎內(nèi)層事務只能做個標記,來設(shè)置當前事務只能回滾。
緊接著它想拋出錯誤,但是由于被 try catch 了,于是乎正常執(zhí)行后續(xù)的邏輯,等執(zhí)行到最后,外層要提交事務了,發(fā)現(xiàn)當前事務已經(jīng)被打了回滾的標記,所以提交失敗,報了上面的錯。
具體原理可以看我之前的那篇文章,這里簡單舉例下會出錯的示例代碼:
大致就是下面這個代碼調(diào)用邏輯,有一個 service 標記了 @Transcational,采用默認的事務傳播機制:
緊接著 UserService#insert 調(diào)用了 addressService#errorInvoker,這個方法也標記了 @Transcational:
這樣一來,只要 addressService#errorInvoker 的調(diào)用發(fā)生報錯,那么必然能重現(xiàn)上面的報錯信息。
原理很清晰,我不可能犯這個錯。
我信誓旦旦的對測試說:這肯定是老陳寫的 bug,與我無關(guān)!
老陳瞄了我一眼:老子已經(jīng) 2 個月沒碰過那個項目了,你扯犢子呢?
隨后這個老測試直接把更詳細的報錯扔了過來,咳咳,涉及公司的類名這里不展示的,反正確實是我的代碼....
但是我還是覺得很不可思議,這部分邏輯是我新寫的,我壓根就沒有使用嵌套事務啊!
大致的代碼如下:
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean xxx(xxx dto) {
list1 = .....;
try {
數(shù)據(jù)庫批量保存list1;
} catch (Exception e) {
if (e instanceof DuplicateKeyException) {
//篩選過濾重復 key 的數(shù)據(jù)
//打標發(fā)送
數(shù)據(jù)庫批量保存過濾之后的list1;
}
....
}
sendToMQ(xxx);
list2 = .....;
try {
數(shù)據(jù)庫批量保存list2;
} catch (Exception e) {
if (e instanceof DuplicateKeyException) {
//篩選過濾重復 key 的數(shù)據(jù)
//打標發(fā)送
數(shù)據(jù)庫批量保存過濾之后的list2;
}
...
}
sendToMQ(xxx);
return Boolean.TRUE;
}這個接口其實是一次性接口,用來補數(shù)據(jù)的,線上跑過一次后,后面應該不會再使用。
出于保險原則,兼容上游部分數(shù)據(jù)重復調(diào)用,所以我做了重復key的處理,剔除重復的部分,讓不重復的部分正常保存。
正常情況下不會出現(xiàn)這個場景,剛好測試環(huán)境測試來回折騰有很多重復數(shù)據(jù)(其實我這樣寫也是為了兼容測試,隨便他折騰)
這里的代碼邏輯不復雜,明面上來看,我并沒有調(diào)用別的 service !也并不存在嵌套事務的問題,所以我思來想去也看不明白。
于是......
我出門放了個水,順帶逛了一圈,接著買了杯咖啡,遇事不決,量子力...個屁,立馬屁顛屁顛的跑回來繼續(xù)看代碼。
回來突然就看 try-catch 不爽。
但是 try 里面就是一個 mybatis-plus 的 IService,批量保存數(shù)據(jù)的操作。
難道它有什么騷操作?點進去一看突然發(fā)現(xiàn):
我丟!
喚起了我的記憶,mybatis-plus 為了保證批量保存的事務性,加了 @Transactional。
合著我確實沒想著使用嵌套事務,但是這被迫上了“賊船”?。?/p>
這本是好意,但是在我這個場景有點麻,它完美的復現(xiàn)了上文提到的那個錯誤使用,在有重復 key 的場景確實報錯了,但是被外層 try-catch 攔住了拋錯,不過事務上已經(jīng)打了失敗的標了!
解決辦法其實很簡單:
- 把 saveBatch 上的 @Transactional 注解刪了,很明顯我做不到,這是 mybatisplus 的源碼。
- 把 saveBatch 上的 @Transactional 注解上設(shè)置事務傳播機制為:REQUIRES_NEW 或 NESTED,很明顯,我也做不到,這是 mybatis-plus 的源碼。
然后我找了下,好像也沒有什么參數(shù)可以指定 saveBatch 的事務傳播機制。
所以咋辦。。。測試還在催我,沒辦法,只能不用 mybatis-plus 的 saveBatch ,自己通過 mapper 寫個批量插入了:
一波操作提交代碼重啟服務,讓測試再試試,且輕飄飄的甩一句:這不是我的bug,我被框架坑了。
咳咳,反正我不管,我的代碼沒有bug,這是程序員最后的倔強。
最后
所以在使用三方代碼的情況下還是需要多留個心眼點點看。
我記得以前還聽說過一個段子,就是有個人用了一個網(wǎng)上的組件,正常情況下都沒事,異常情況下,系統(tǒng)就掛了。
后面一找,那個組件在個角落嘎達寫了 System.exit。
對上面這個錯誤源碼層面分析有興趣的話 ,可以看下我之前的這篇文章群里分享的面試題,第一題就不會了?
本文標題:小心MybatisPlus的一個坑與面試題
文章分享:http://www.dlmjj.cn/article/dpipcop.html


咨詢
建站咨詢
