最近在撰寫論文,參考了大量文獻,也在閱讀博文的過程當中對架構有了新的認識,發現原文章Spring 事務管理因侷限於Hibernate
框架,未對NESTED
級別的事務作詳述,特寫本文進行補充。java
正常的邏輯:mysql
形成了須要編寫許多關於事務的冗餘代碼,爲了解決此問題,Spring
採用聲明式事務。spring
Spring Boot
的核心配置中已經默認啓用了事務,使用Transactional
註解即爲方法添加事務:sql
Spring
事務註解配置以下,比較主要的就是isolation
和propagation
了。數據庫
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Transactional { @AliasFor("transactionManager") String value() default ""; @AliasFor("value") String transactionManager() default ""; Propagation propagation() default Propagation.REQUIRED; Isolation isolation() default Isolation.DEFAULT; int timeout() default -1; boolean readOnly() default false; Class<? extends Throwable>[] rollbackFor() default {}; String[] rollbackForClassName() default {}; Class<? extends Throwable>[] noRollbackFor() default {}; String[] noRollbackForClassName() default {}; }
isolation
爲事務的隔離級別,講了好多遍了,不作贅述。segmentfault
public enum Isolation { DEFAULT(-1), READ_UNCOMMITTED(1), READ_COMMITTED(2), REPEATABLE_READ(4), SERIALIZABLE(8); private final int value; private Isolation(int value) { this.value = value; } public int value() { return this.value; } }
propagation
爲事務傳播級別,Spring
共配置了7
種傳播級別,原文章已對前六種作過詳述,本文一塊兒來學習Hibernate
不支持的NESTED
傳播級別。mybatis
public enum Propagation { REQUIRED(0), SUPPORTS(1), MANDATORY(2), REQUIRES_NEW(3), NOT_SUPPORTED(4), NEVER(5), NESTED(6); private final int value; private Propagation(int value) { this.value = value; } public int value() { return this.value; } }
因Hibernate
不支持,故本文啓用MyBatis
進行本傳播級別事務的研究。架構
POM
中依賴MyBatis
、MySQL
;爲了演示方便,選用了自動化工具mapper-spring-boot-starter
。app
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>2.1.5</version> </dependency>
實體層:框架
public class Cat { private Long id; private String name; } public class Dog { private Long id; private String name; }
數據訪問層:
public interface CoreMapper<T> extends Mapper<T>, MySqlMapper<T> { } @Mapper public interface CatMapper extends CoreMapper<Cat> { } @Mapper public interface DogMapper extends CoreMapper<Dog> { }
相似於JPA
,對於簡單的數據庫操做,經過繼承Mapper
和MySqlMapper
接口,不須要寫一行SQL
,同時開啓駝峯映射,XML
也不用寫。
服務層兩個保存方法:
@Transactional(propagation = Propagation.NESTED) @Override public void save(Cat cat) { catMapper.insertUseGeneratedKeys(cat); } @Transactional(propagation = Propagation.NESTED) @Override public void save(Dog dog) { dogMapper.insertUseGeneratedKeys(dog); }
寫個方法測試一下:
public void test() { catService.save(new Cat("Hello Kitty")); dogService.save(new Dog("史努比")); }
數據保存成功,數據訪問層配置沒有問題。
若是當前存在事務,則在當前事務的一個嵌套事務中運行。
test
方法開始事務,調用cat
和dog
的保存方法,dog
的保存方法中拋出了RuntimeException
異常。
@Transactional public void test() { catService.save(new Cat("Hello Kitty")); dogService.saveAndThrowException(new Dog("史努比")); }
執行test
方法,兩張表的數據都沒有存上。不該該是兩個子事務嗎?dog
事務回滾,爲何cat
也存不上呢?
緣由以下,test
方法開啓了事務,CatService
與DogService
在NESTED
的傳播級別下分別創建了子事務,嵌套運行,DogService
拋出了異常,子事務回滾,不影響父事務。
可是父事務沒有捕獲RuntimeException
,父事務回滾,父事務的回滾會使子事務回滾,因此CatService
的子事務也回滾了,形成了兩張表的數據都沒存上。
父事務的提交和回滾會使其子事務提交或回滾。
這個層面並非NESTED
的所有,由於所有設置成REQUIRED
三個方法共享一個事務也能實現相同的功能。
對上述方法加以修改,添加一個簡易的異常處理,再運行。
@Transactional public void test() { catService.save(new Cat("Hello Kitty")); try { dogService.saveAndThrowException(new Dog("史努比")); } catch (RuntimeException e) { e.printStackTrace(); } }
cat
存上了,dog
沒存上。
子事務的提交或回滾不影響父事務的提交或回滾,這裏DogService
的子事務回滾,向上拋出的異常被處理,父事務不回滾,事務提交。
學習完特性可能還每碰到過應用場景,我有幸碰到過一次,舉例以下:
將事務所有修改成默認的REQUIRED
級別從新運行上述代碼:
@Transactional public void test() { catService.save(new Cat("Hello Kitty")); try { dogService.saveAndThrowException(new Dog("史努比")); } catch (RuntimeException e) { e.printStackTrace(); } } @Transactional @Override public void save(Cat cat) { catMapper.insertUseGeneratedKeys(cat); } @Transactional @Override public void saveAndThrowException(Dog dog) { this.save(dog); throw new RuntimeException(); }
以下圖所示,兩張表都沒存上數據:
且控制檯報錯:
Transaction rolled back because it has been marked as rollback-only.
DogService
拋出了異常,將事務標記爲回滾,雖然test
方法中處理了該異常,可是事務已被標記,致使數據存儲失敗。
兩相對比之下,NESTED
適合容許失敗的場景,我遇到的就是軟刪除場景:
try { hardDelete(); } catch(Exception e) { softDelete(); }
若是配置爲REQUIRED
,事務被標記,即便處理異常,仍然回滾,數據軟刪除失敗。此處,能夠將hardDelete
設置爲NESTED
,由於該場景下容許hardDelete
失敗,hardDelete
做爲子事務,讓調用方決定是否回滾。
項目中採用Hibernate
,不支持NESTED
,爲了規避該問題,將傳播級別設置爲REQUIRES_NEW
,掛起當前事務,新建事務進行回滾,不影響調用方的事務。雖然能實現功能,但理論上,仍是NESTED
更符合邏輯。
雖然有這麼多隔離級別,可是REQUIRED
和SUPPORTS
已經能知足大多數的開發需求了。
數據庫寫INSERT/UPDATE/DELETE
使用REQUIRED
,讀SELECT
使用SUPPORTS
,遇到異常,再分析使用其餘事務傳播級別。
任何理論都不如現實具體。——沈從文