為什么catch了異常,但事務(wù)還是回滾了?

前幾天我發(fā)了這篇文章《我來出個題:這個事務(wù)會不會回滾?》(https://blog.didispace.com/will-this-transcation-rollback/)
得到了很多不錯的反饋,也有不少讀者通過微信、群或者郵件的方式,給了我一些關(guān)于test4的回復(fù)。其中還有直接發(fā)給我測試案例,來證明我的答案是錯的。
今天,我們就來一起看看test4這個爭議很大的問題。如果您是剛打開這篇文章,不了解我們在討論啥,那可以先點擊查看之前的這篇《我來出個題:這個事務(wù)會不會回滾?》(https://blog.didispace.com/will-this-transcation-rollback/)。
通過這兩篇文章的解析,相信你會對Spring Data JPA下的事務(wù)執(zhí)行機制有質(zhì)的飛躍。
為什么沒回滾
先來說說,那些寫了代碼驗證"不會回滾"的情況,把這些錯誤答案的原因先說清楚,然后再細說test4會回滾的情況。
根據(jù)這兩天讀者給我的案例或者描述清楚的一些情況,歸結(jié)了一下,大家寫的驗證代碼之所以不會回滾,主要有以下三個原因:
沒有按照我題目開頭說的,采用InnoDB存儲引擎,用了MyISAM,不支持事務(wù),自然不會復(fù)現(xiàn)。 沒用按照我題目開頭說的,采用JPA和JSR 303校驗注解,比如:用了MyBaits,所以自然也不會復(fù)現(xiàn)。 定義事務(wù)的函數(shù)不是public類型,這個基礎(chǔ)用法就不對了,事務(wù)本身就沒生效
歸家一下出現(xiàn)這些疑問的原因:沒審題和事務(wù)基礎(chǔ)掌握不牢導(dǎo)致。關(guān)于事務(wù)基礎(chǔ)使用的一些常見注意點,之前寫過一篇文章,如果覺得這方面知識還不扎實的,建議讀一讀:《為什么加了@Transactional注解,事務(wù)沒有回滾?》(https://blog.didispace.com/transactional-not-rollback/)
為什么寫了catch,還會回滾
先來看看執(zhí)行時候報的異常:
javax.validation.ConstraintViolationException: Validation failed for classes [com.didispace.chapter310.User] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='個數(shù)必須在0和5之間', propertyPath=name, rootBeanClass=class com.didispace.chapter310.User, messageTemplate='{javax.validation.constraints.Size.message}'}
]
at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate(BeanValidationEventListener.java:140) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.onPreInsert(BeanValidationEventListener.java:80) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.action.internal.EntityInsertAction.preInsert(EntityInsertAction.java:209) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:83) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:478) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:356) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1454) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:511) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3283) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2479) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:473) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:178) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$300(JdbcResourceLocalTransactionCoordinatorImpl.java:39) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:271) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:98) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
這個異常是這個回滾的關(guān)鍵。這個異常javax.validation.ConstraintViolationException是哪里的呢?還記得以前說的JSR 303不?對的,是Bean Validation中的異常。
有的讀者說這個不是RuntimeException,所以不會回滾。很顯然,這類判斷的都沒有實際嘗試一下,只要點開源碼可以馬上發(fā)現(xiàn),這個異常就是屬于RunTimeException的。
實際上,之所以會回滾,與這里使用Spring Data JPA以及Hibernate Validator有直接關(guān)系。從JPA 2.0開始,就默認支持了這些Bean Validation的實現(xiàn),它提供了實體生命周期中pre-persist, pre-update,pre-remove三個事件發(fā)生時來執(zhí)行校驗的功能。而在校驗的時候,當(dāng)校驗失敗,拋出javax.validation.ConstraintViolationException時,當(dāng)前事務(wù)就會被標(biāo)記為rollback。
源碼解析
要想了解,這其中到底發(fā)生了什么,跟蹤源碼是最好的方式。那么源碼從哪里開始看呢?從異常日志中找線索吧。

從異常棧中找到最近的一個錯誤,點開看看。

錯誤行數(shù)在532行tx.commit(),習(xí)慣性的加上斷點,這樣下一次進來的時候可以看看當(dāng)前情況下的各種參數(shù)情況。
同時看到下面還有個catch,既然532行出錯了,那這里肯定會進,所以也加個端點,到時候可以進去看看。
執(zhí)行程序,調(diào)用一下test4,執(zhí)行到532行,然后進入下一步,看看會到哪里?
這個時候,會進入到org.hibernate.engine.transaction.internal.TransactionImpl,具體位置如下:

還是習(xí)慣性的,在下面兩行重要位置加上斷點,以便下次可以快速到這里。
繼續(xù)按上看的步驟嘗試下去,可以來到下圖的位置:

可以看到校驗異常是從271行出來的,結(jié)合278行和280行,是不是清楚這里回滾的原因了呢?
小結(jié)
當(dāng)我把上一篇問題推到很多地方之后,其實還是收到了不少負面的反饋,甚至還有說我誤導(dǎo)讀者,順便問候了下我的祖宗。這些我就不跟愛噴的讀者互杠了,我是一直都推崇碰到問題,盡量多深挖一些的學(xué)習(xí)方式。雖然有的時候出現(xiàn)問題,確實是由于不恰當(dāng)?shù)膶懛▽?dǎo)致,但如果你沒有理解這個錯誤的原因,本質(zhì)還是對其底層邏輯不夠了解。如果你能從問題中找到線索,并順藤摸瓜地探究和思考,你能夠收獲到的東西,遠比噴我來的強。
實踐出真知,當(dāng)你覺得困惑的時候,不如動手寫一寫,調(diào)一調(diào),很多答案就能自然浮現(xiàn)!如果對于test4會回滾還不夠理解的讀者,那就跟著我上面的步驟,一步步嘗試一下,可以觀察的更深入一些,你對這部分邏輯的理解就更全面了。
我們正在組建高質(zhì)量的Spring技術(shù)交流群,最近這個分享的源頭也是來源于群里的一次討論。歡迎各種熱愛技術(shù)的開發(fā)者加入?yún)⑴c討論。這里的每個人都有自己的閃光點,互相學(xué)習(xí),取長補短,長期堅持,愿大家都會成為自己領(lǐng)域里的佼佼者!
往期推薦
原創(chuàng)不易,如果您覺得本文不錯
歡迎轉(zhuǎn)發(fā)到朋友圈,支持一下我的努力吧!
