導致 Spring 事務(wù)失效的場景有哪些,如何解決失效問題心包?

實際項目開發(fā)中类咧,如果涉及到多張表操作時,為了保證業(yè)務(wù)數(shù)據(jù)的一致性蟹腾,大家一般都會采用事務(wù)機制痕惋;好多小伙伴可能只是簡單了解一下,遇到事務(wù)失效的情況娃殖,便會無從下手值戳,此篇文章給大家整理了一下常見Spring事務(wù)失效的場景,希望開發(fā)過程盡量避免踩坑珊随,造成時間精力的浪費述寡。

按照最基本的使用方式以及常見失效場景優(yōu)先級整理,先簡單介紹一下具體失效場景:

注解@Transactional配置的方法非public權(quán)限修飾;

注解@Transactional所在類非Spring容器管理的bean叶洞;

注解@Transactional所在類中鲫凶,注解修飾的方法被類內(nèi)部方法調(diào)用;

業(yè)務(wù)代碼拋出異常類型非RuntimeException衩辟,事務(wù)失效螟炫;

業(yè)務(wù)代碼中存在異常時,使用try…catch…語句塊捕獲艺晴,而catch語句塊沒有throw new RuntimeExecption異常;(最難被排查到問題且容易忽略)

注解@Transactional中Propagation屬性值設(shè)置錯誤即Propagation.NOT_SUPPORTED(一般不會設(shè)置此種傳播機制)

mysql關(guān)系型數(shù)據(jù)庫昼钻,且存儲引擎是MyISAM而非InnoDB掸屡,則事務(wù)會不起作用(基本開發(fā)中不會遇到);

下面基于以上場景然评,給小伙伴們詳細解釋仅财;

一、非public權(quán)限修飾

參考Spring官方文檔介紹碗淌,摘要盏求、譯文如下:

When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

譯文

使用代理時,您應(yīng)該只將@Transactional注釋應(yīng)用于具有公共可見性的方法亿眠。如果使用@Transactional注釋對受保護的碎罚、私有的或包可見的方法進行注釋,則不會引發(fā)錯誤纳像,但帶注釋的方法不會顯示配置的事務(wù)設(shè)置荆烈。如果需要注釋非公共方法,請考慮使用AspectJ(見下文)竟趾。

簡言之:@Transactional 只能用于 public 的方法上憔购,否則事務(wù)不會失效,如果要用在非 public 方法上岔帽,可以開啟 AspectJ 代理模式倦始。

目前,如果@Transactional注解作用在非public方法上山卦,編譯器也會給與明顯的提示,如圖:

二诵次、非Spring容器管理的bean

基于這種失效場景账蓉,有工作經(jīng)驗的大佬基本上是不會存在這種錯誤的;@Service注解注釋逾一,StudentServiceImpl類則不會被Spring容器管理铸本,因此即使方法被@Transactional注解修飾,事務(wù)也亦然不會生效遵堵。

簡單舉例如下:

/**

*@Author:qxy

*/

//@Service

publicclassStudentServiceImplimplementsStudentService{

@Autowired

privateStudentMapper?studentMapper;

@Autowired

privateClassService?classService;

@Override

@Transactional(propagation?=?Propagation.REQUIRED,?rollbackFor?=?Exception.class)

publicvoidinsertClassByException(StudentDostudentDo)throwsCustomException

{

studentMapper.insertStudent(studentDo);

thrownewCustomException();

}

}

三箱玷、注解修飾的方法被類內(nèi)部方法調(diào)用

這種失效場景是我們?nèi)粘i_發(fā)中最常踩坑的地方;在類A里面有方法a 和方法b陌宿,然后方法b上面用@Transactional加了方法級別的事務(wù)锡足,在方法a里面 調(diào)用了方法b, 方法b里面的事務(wù)不會生效壳坪。為什么會失效呢舶得?:

其實原因很簡單,Spring在掃描Bean的時候會自動為標注了@Transactional注解的類生成一個代理類(proxy),當有注解的方法被調(diào)用的時候爽蝴,實際上是代理類調(diào)用的沐批,代理類在調(diào)用之前會開啟事務(wù)纫骑,執(zhí)行事務(wù)的操作,但是同類中的方法互相調(diào)用九孩,相當于this.B()先馆,此時的B方法并非是代理類調(diào)用,而是直接通過原有的Bean直接調(diào)用躺彬,所以注解會失效煤墙。

@Service

publicclassClassServiceImplimplementsClassService{

@Autowired

privateClassMapper?classMapper;

publicvoidinsertClass(ClassDo?classDo)throwsCustomException{

insertClassByException(classDo);

}

@Override

@Transactional(propagation?=?Propagation.REQUIRED)

publicvoidinsertClassByException(ClassDo?classDo)throwsCustomException{

classMapper.insertClass(classDo);

thrownewRuntimeException();

}

}

//測試用例:

@Test

publicvoidinsertInnerExceptionTest()throwsCustomException{

classDo.setClassId(2);

classDo.setClassName("java_2");

classDo.setClassNo("java_2");

classService.insertClass(classDo);

}

測試結(jié)果:

java.lang.RuntimeException

at?com.qxy.common.service.impl.ClassServiceImpl.insertClassByException(ClassServiceImpl.java:34)

at?com.qxy.common.service.impl.ClassServiceImpl.insertClass(ClassServiceImpl.java:27)

at?com.qxy.common.service.impl.ClassServiceImpl$$FastClassBySpringCGLIB$$a1c03d8.invoke()

雖然業(yè)務(wù)代碼報錯了,但是數(shù)據(jù)庫中已經(jīng)成功插入數(shù)據(jù)顾患,事務(wù)并未生效番捂;

解決方案

類內(nèi)部使用其代理類調(diào)用事務(wù)方法:以上方法略作改動

publicvoidinsertClass(ClassDo?classDo)throwsCustomException{

//?????????insertClassByException(classDo);

((ClassServiceImpl)AopContext.currentProxy()).insertClassByException(classDo);

}

//測試用例:

@Test

publicvoidinsertInnerExceptionTest()throwsCustomException{

classDo.setClassId(3);

classDo.setClassName("java_3");

classDo.setClassNo("java_3");

classService.insertClass(classDo);

}

業(yè)務(wù)代碼拋出異常,數(shù)據(jù)庫未插入新數(shù)據(jù)江解,達到我們的目的设预,成功解決一個事務(wù)失效問題;

數(shù)據(jù)庫數(shù)據(jù)未發(fā)生改變犁河;

注意:一定要注意啟動類上要添加@EnableAspectJAutoProxy(exposeProxy = true)注解鳖枕,否則啟動報錯:

java.lang.IllegalStateException:?Cannot?find?current?proxy:?Set'exposeProxy'property?on?Advised?to'true'to?make?it?available.

at?org.springframework.aop.framework.AopContext.currentProxy(AopContext.java:69)

at?com.qxy.common.service.impl.ClassServiceImpl.insertClass(ClassServiceImpl.java:28)

四、異常類型非RuntimeException

這種事務(wù)失效場景也是非常難排查問題的桨螺,如果沒有深究源碼實現(xiàn)宾符,估計要花費一番功夫啦;

@Service

publicclassClassServiceImplimplementsClassService{

@Autowired

privateClassMapper?classMapper;

//????@Override

//????@Transactional(propagation?=?Propagation.NESTED,?rollbackFor?=?Exception.class)

publicvoidinsertClass(ClassDo?classDo)throwsException{

//????????即使此處使用代理對象調(diào)用內(nèi)部事務(wù)方法灭翔,數(shù)據(jù)依然未發(fā)生回滾魏烫,事務(wù)機制亦然失效

((ClassServiceImpl)AopContext.currentProxy()).insertClassByException(classDo);

}

@Override

@Transactional(propagation?=?Propagation.REQUIRED)

publicvoidinsertClassByException(ClassDo?classDo)throwsException{

classMapper.insertClass(classDo);

//拋出非RuntimeException類型

thrownewException();

}

//測試用例:

@Test

publicvoidinsertInnerExceptionTest()throwsException{

classDo.setClassId(3);

classDo.setClassName("java_3");

classDo.setClassNo("java_3");

classService.insertClass(classDo);

}

}

運行結(jié)果:

業(yè)務(wù)代碼拋出異常,但是數(shù)據(jù)庫發(fā)生更新操作肝箱;

java.lang.Exception

at?com.qxy.common.service.impl.ClassServiceImpl.insertClassByException(ClassServiceImpl.java:35)

at?com.qxy.common.service.impl.ClassServiceImpl$$FastClassBySpringCGLIB$$a1c03d8.invoke()

at?org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)

數(shù)據(jù)庫依然插入數(shù)據(jù)哄褒,不是我們想要的結(jié)果啊,趕緊修改吧煌张,產(chǎn)品經(jīng)理來追啦~

解決方案:

@Transactional注解修飾的方法呐赡,加上rollbackfor屬性值,指定回滾異常類型:@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)

@Override

@Transactional(propagation?=?Propagation.REQUIRED,rollbackFor?=?Exception.class)

publicvoidinsertClassByException(ClassDoclassDo)throwsException

{

classMapper.insertClass(classDo);

thrownewException();

}

五骏融、捕獲異常后链嘀,卻未拋出異常

在事務(wù)方法中使用try-catch,導致異常無法拋出档玻,自然會導致事務(wù)失效怀泊。

@Service

publicclassClassServiceImplimplementsClassService{

@Autowired

privateClassMapper?classMapper;

//????@Override

publicvoidinsertClass(ClassDo?classDo){

((ClassServiceImpl)AopContext.currentProxy()).insertClassByException(classDo);

}

@Override

@Transactional(propagation?=?Propagation.REQUIRED,rollbackFor?=?Exception.class)

publicvoidinsertClassByException(ClassDoclassDo)

{

classMapper.insertClass(classDo);

try{

inti?=1/0;

}catch(Exception?e)?{

e.printStackTrace();

}

}

}

//?測試用例:

@Test

publicvoidinsertInnerExceptionTest(){

classDo.setClassId(4);

classDo.setClassName("java_4");

classDo.setClassNo("java_4");

classService.insertClass(classDo);

}

執(zhí)行結(jié)果:

解決方案:捕獲異常并拋出異常

@Override

@Transactional(propagation?=?Propagation.REQUIRED,rollbackFor?=?Exception.class)

publicvoidinsertClassByException(ClassDoclassDo)

{

classMapper.insertClass(classDo);

try{

inti?=1/0;

}catch(Exception?e)?{

e.printStackTrace();

thrownewRuntimeException();

}

}

六、事務(wù)傳播行為設(shè)置異常

此種事務(wù)傳播行為不是特殊自定義設(shè)置窃肠,基本上不會使用Propagation.NOT_SUPPORTED包个,不支持事務(wù)

@Transactional(propagation?=?Propagation.NOT_SUPPORTED,rollbackFor?=?Exception.class)

publicvoidinsertClassByException(ClassDoclassDo)

{

classMapper.insertClass(classDo);

try{

inti?=1/0;

}catch(Exception?e)?{

e.printStackTrace();

thrownewRuntimeException();

}

}

六、數(shù)據(jù)庫存儲引擎不支持事務(wù)

以MySQL關(guān)系型數(shù)據(jù)為例,如果其存儲引擎設(shè)置為 MyISAM碧囊,則事務(wù)失效树灶,因為MyISMA 引擎是不支持事務(wù)操作的;

故若要事務(wù)生效糯而,則需要設(shè)置存儲引擎為InnoDB 天通;目前 MySQL 從5.5.5版本開始默認存儲引擎是:InnoDB。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末熄驼,一起剝皮案震驚了整個濱河市像寒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瓜贾,老刑警劉巖诺祸,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異祭芦,居然都是意外死亡筷笨,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門龟劲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胃夏,“玉大人,你說我怎么就攤上這事昌跌⊙鲑鳎” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵蚕愤,是天一觀的道長答恶。 經(jīng)常有香客問我,道長萍诱,這世上最難降的妖魔是什么亥宿? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮砂沛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘曙求。我一直安慰自己碍庵,他們只是感情好,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布悟狱。 她就那樣靜靜地躺著静浴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挤渐。 梳的紋絲不亂的頭發(fā)上苹享,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機與錄音,去河邊找鬼得问。 笑死囤攀,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的宫纬。 我是一名探鬼主播焚挠,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼漓骚!你這毒婦竟也來了蝌衔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤蝌蹂,失蹤者是張志新(化名)和其女友劉穎噩斟,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孤个,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡剃允,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了硼身。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片硅急。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖佳遂,靈堂內(nèi)的尸體忽然破棺而出营袜,到底是詐尸還是另有隱情,我是刑警寧澤丑罪,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布荚板,位于F島的核電站,受9級特大地震影響吩屹,放射性物質(zhì)發(fā)生泄漏跪另。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一煤搜、第九天 我趴在偏房一處隱蔽的房頂上張望免绿。 院中可真熱鬧,春花似錦擦盾、人聲如沸嘲驾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辽故。三九已至,卻和暖如春腐碱,著一層夾襖步出監(jiān)牢的瞬間誊垢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留喂走,地道東北人殃饿。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像缴啡,于是被迫代替她去往敵國和親壁晒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容