前幾天我發了這篇文章《我來出個題:這個事務會不會回滾?》得到了很多不錯的反饋,也有不少讀者通過微信、群或者郵件的方式,給了我一些關于test4的回復,其中還有直接發給我測驗案例,來證明我的答案是錯的,今天,我們就來一起看看test4這個爭議很大的問題,如果您是剛打開這篇文章,不了解我們在討論啥,那可以先點擊查看之前的這篇《我來出個題:這個事務會不會回滾?》,通過這兩篇文章的決議,相信你會對Spring Data JPA下的事務執行機制有質的飛躍,
為什么沒回滾
先來說說,那些寫了代碼驗證"不會回滾"的情況,把這些錯誤答案的原因先說清楚,然后再細說test4會回滾的情況,
根據這兩天讀者給我的案例或者描述清楚的一些情況,歸結了一下,大家寫的驗證代碼之所以不會回滾,主要有以下三個原因:
- 沒有按照我題目開頭說的,采用InnoDB存盤引擎,用了MyISAM,不支持事務,自然不會復現,
- 沒用按照我題目開頭說的,采用JPA和JSR 303校驗注解,比如:用了MyBaits,所以自然也不會復現,
- 定義事務的函式不是public型別,這個基礎用法就不對了,事務本身就沒生效
歸家一下出現這些疑問的原因:沒審題和事務基礎掌握不牢導致,關于事務基礎使用的一些常見注意點,之前寫過一篇文章,如果覺得這方面知識還不扎實的,建議讀一讀:《為什么加了@Transactional注解,事務沒有回滾?》
為什么寫了catch,還會回滾
先來看看執行時候報的例外:
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='個數必須在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]
這個例外是這個回滾的關鍵,這個例外javax.validation.ConstraintViolationException是哪里的呢?還記得以前說的JSR 303不?對的,是Bean Validation中的例外,
有的讀者說這個不是RuntimeException,所以不會回滾,很顯然,這類判斷的都沒有實際嘗試一下,只要點開原始碼可以馬上發現,這個例外就是屬于RunTimeException的,
實際上,之所以會回滾,與這里使用Spring Data JPA以及Hibernate Validator有直接關系,從JPA 2.0開始,就默認支持了這些Bean Validation的實作,它提供了物體生命周期中pre-persist, pre-update,pre-remove三個事件發生時來執行校驗的功能,而在校驗的時候,當校驗失敗,拋出javax.validation.ConstraintViolationException時,當前事務就會被標記為rollback,
原始碼決議
要想了解,這其中到底發生了什么,跟蹤原始碼是最好的方式,那么原始碼從哪里開始看呢?從例外日志中找線索吧,

從例外堆疊中找到最近的一個錯誤,點開看看,

錯誤行數在532行tx.commit(),習慣性的加上斷點,這樣下一次進來的時候可以看看當前情況下的各種引數情況,
同時看到下面還有個catch,既然532行出錯了,那這里肯定會進,所以也加個端點,到時候可以進去看看,
執行程式,呼叫一下test4,執行到532行,然后進入下一步,看看會到哪里?
這個時候,會進入到org.hibernate.engine.transaction.internal.TransactionImpl,具體位置如下:

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

可以看到校驗例外是從271行出來的,結合278行和280行,是不是清楚這里回滾的原因了呢?
實踐出真知,當你覺得困惑的時候,不如動手寫一寫,調一調,很多答案就能自然浮現!
如果對于test4會回滾還不夠理解,或者你還有其他事務執行不如預期的讀者,那就跟著我的思路,一步步嘗試一下,可以觀察的更深入一些,你對這部分邏輯的理解就更全面了,我們正在組建高質量的Spring技術交流群,歡迎各種熱愛技術的開發者加入參與討論,這里的每個人都有自己的閃光點,互相學習,取長補短,長期堅持,愿大家都會成為自己領域里的佼佼者!
歡迎關注我的公眾號:程式猿DD,分享外面看不到的干貨與思考!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/288832.html
標籤:Java
