JPA事务示例分析
在這個工程中,定義一個名為User的實體:
@Entity @Data @NoArgsConstructor public class User {@Id@GeneratedValueprivate Long id;@Size(max = 5)private String name;@Max(50)private Integer age;public User(String name, Integer age) {this.name = name;this.age = age;}}這里name設置了長度為5,這樣可以通過insert語句中的name超長,讓其拋出異常,從而可以測試事務的觸發。
另外工程中還包含了Spring Data Jpa的數據訪問對象UserRepository,用來實現對User實體的數據操作,這里就不放具體代碼了。
問題來了
這里數據庫采用MySQL 5.7,存儲引擎為InnoDB,使用默認事務級別。
下面來調整下這四個問題吧:
問題一:test1會不會回滾?-- 回滾
@Transactional public void test1() {userRepository.save(new User("AAA", 10));throw new RuntimeException(); }問題二:test2會不會回滾?-- 不回滾
@Transactional public void test2() {userRepository.save(new User("AAA", 10));try {throw new RuntimeException();} catch (Exception e) {log.error("異常捕獲:", e);} }問題三:test3會不會回滾?(第二句插入name超長)-- 回滾
@Transactional public void test3() {userRepository.save(new User("AAA", 10));userRepository.save(new User("1234567890", 20)); }問題四:test4會不會回滾?(第二句插入name超長)-- 回滾
@Transactional public void test4() {userRepository.save(new User("AAA", 10));try {userRepository.save(new User("1234567890", 20));} catch (Exception e) {log.error("異常捕獲:", e);} }為什么寫了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行,是不是清楚這里回滾的原因了呢?
為什么加了@Transactional注解,事務沒有回滾?
@Transactional注解不生效,是Spring使用者非常常見的一類問題,上面我們講了一種,其他還有一些可能的原因,這里作為擴展閱讀一并列出。
如果你當前碰到的原因不是上面的情況,那就看看下面這幾種情況是否存在:
1.@Transactional注解修飾的函數中catch了異常,并沒有往方法外拋。不過,也有一寫復雜場景可能不一樣,比如我這里出的四個題中的test4:我來出個題:這個事務會不會回滾?
2.@Transactional注解修飾的函數不是public類型
3.異常類型錯誤,如果有通過rollbackFor指定回滾的異常類型,那么拋出的異常與指定的是否一致。
4.數據源沒有配置事務管理器
5.在一個類中調用自己的方法。建議分開寫,互相調用。
6.對應數據庫使用的存儲引擎不支持事務,比如:MyISAM。
總結
- 上一篇: html没有插件怎么办,html网页包插
- 下一篇: EasyMock 简介