深刻理解Spring宣告式事務

Awecoder發表於2021-11-21

問題引入

  1. Spring中事務傳播有哪幾種,分別是怎樣的?
  2. 理解註解事務的自動配置?
  3. SpringBoot啟動類為什麼不需要加@EnableTransactionManagement註解?
  4. 宣告式事務的實現原理?(待補充)

1 宣告式事務

系統開發中必然與資料打交道,事務管理必不可少。Spring支援宣告式事務,通過@Transactional註解控制方法是否支援事務。宣告式事務,基於AOP實現,將具體業務和業務邏輯解耦。

Spring提供了@EnableTransactionManagement註解在配置類(啟動類)上啟用支援事務,此時Spring會自動掃描具有@Transactional註解的類和方法。該註解相當於xml配置方式的 <tx:annotation-driven />。通過設定mode屬性,決定使用spring代理,還是ASPECTJ擴充套件。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
   boolean proxyTargetClass() default false;
   AdviceMode mode() default AdviceMode.PROXY; // 代理模式
   int order() default Ordered.LOWEST_PRECEDENCE; // LOWEST_PRECEDENCE最低優先順序,所以在執行鏈的最外面,而自己實現的AOP攔截器優先順序都高於事務,所以被巢狀在裡面,越貼近業務程式碼。
}

2 @Transactional註解的使用

2.1 @Transactional註解屬性

@Transactional註解可以應用於類和方法。宣告類時,該註解預設作用於類和子類的所有方法,應用於public方法才有效;父類方法要加入同等級別的註解,需要單獨宣告。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

	@AliasFor("transactionManager")
	String value() default "";
    
    // 用來確定目標事務管理器
	@AliasFor("value")
	String transactionManager() default "";

    // 事務的傳播,預設Propagation.REQUIRED
	Propagation propagation() default Propagation.REQUIRED;

    // 事務隔離級別,預設是Isolation.DEFAULT
	Isolation isolation() default Isolation.DEFAULT;

    // 事務超時時間,預設是-1
	int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

    // 指定事務是否為只讀事務,預設為false,僅僅是個提示
	boolean readOnly() default false;

    // 標識能觸發事務回滾的異常型別,預設是RuntimeException和Error,不包含檢查異常。
	Class<? extends Throwable>[] rollbackFor() default {};

	// 標識哪些異常不需要回滾事務
	Class<? extends Throwable>[] noRollbackFor() default {};

	String[] noRollbackForClassName() default {};
	String[] rollbackForClassName() default {};
}

其中,isolation和timeout兩個屬性僅對新啟動的事務有效,專為Propagation.REQUIRED和Propagation.REQUIRES_NEW使用而設計。

2.2 事務的傳播行為-Propagation

Propagation定義了事務的傳播,一共有7種級別。

public enum Propagation {
	REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
	SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
	MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
	REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
	NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
	NEVER(TransactionDefinition.PROPAGATION_NEVER),
	NESTED(TransactionDefinition.PROPAGATION_NESTED);

	private final int value;

	Propagation(int value) {
		this.value = value;
	}

	public int value() {
		return this.value;
	}
}
  • REQUIRED:使用當前的事務,如果當前沒有事務,則自己新建一個事務,子方法是必須執行在一個事務中的;如果當前存在事務,則加入這個事務,成為一個整體。
    舉例:領導沒飯吃,我有錢,那麼我會自己買了吃;領導有的吃,會分給你一起吃。
  • SUPPORTS:如果當前有事務,則使用事務;如果當前沒有事務,則不使用事務。多用於查詢。
    舉例:領導沒飯吃,我也沒飯吃;領導有飯吃,我也有飯吃。
  • MANDATORY:傳播屬性強制必須存在一個事務,如果不存在,則會丟擲異常。
    舉例:領導必須管飯,不管飯就沒飯吃,我就不幹了(就會丟擲異常)。
  • REQUIRES_NEW:如果當前有事務,則掛起該事務,並且自己建立一個新的事務給自己使用;如果當前沒有事務,則同 REQUIRED
    舉例:領導有飯吃,我偏不要,自己買東西自己吃
    • 1.標誌REQUIRES_NEW會新開啟事務,外層事務不會影響內部事務的提交/回滾。內部提交修改,會導致A的髒讀。
    • 2.標誌REQUIRES_NEW的內部事務異常,會影響外部事務的回滾
  • NOT_SUPPORTED:如果當前有事務,則把事務掛起,自己不使用事務執行資料庫操作
    舉例:領導有飯吃,分一點給你,我太忙了,放一邊,我不吃
  • NEVER:如果當前有事務存在,則丟擲異常
    舉例:領導有飯給你吃,我不想吃,果斷丟擲異常
  • NESTED:如果當前存在事務,則開啟子事務(巢狀事務);如果當前沒有事務,則同 REQUIRED。但是如果主事務提交,則會攜帶子事務一起提交。如果主事務回滾,則子事務會一起回滾。相反,子事務異常,則父事務可以回滾或不回滾(trycatch)。
    舉例:領導決策不對,老闆怪罪,領導帶著小弟一同受罪;小弟出了差錯,領導可以推卸責任。

區分NESTED與REQUIRES_NEW

最根本的區別是NESTED還在一個事務中,但是與主事務一塊提交。

// TransactionalServiceImpl
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void testPropagation() {
    stuService.saveParent();
    stuService.saveChildren();
    int a = 1 / 0;
}

// StuServiceImpl
/* 測試事務傳播 */
@Transactional(propagation = Propagation.NESTED) // 切換NESTED/REQUIRES_NEW
@Override
public void saveParent() {
	Stu stu = new Stu();
	stu.setName("parent");
	stu.setScore(100);
	stuMapper.insert(stu);
}

@Transactional(propagation = Propagation.NESTED)
@Override
public void saveChildren() {
	saveChild1();
	int a = 1 / 0;
	saveChild2();
}

一個容易疏漏的點

在代理模式(預設)下,僅攔截通過代理傳入的外部方法呼叫。這意味著同一個目標物件內部的方法呼叫,即使呼叫的方法標記有@Transactional,也不會在執行時導致事務攔截。

// 同一個類
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void saveChildren() {
    saveChild1();
    int a = 1 / 0;
    saveChild2();
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveChild1() {
    Stu stu = new Stu();
    stu.setName("child-1");
    stu.setScore(60);
    stuMapper.insert(stu);
}

參考官方文件

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional. Also, the proxy must be fully initialized to provide the expected behaviour so you should not rely on this feature in your initialization code, i.e. @PostConstruct.
在代理模式下(預設),只有通過代理進入的外部方法呼叫才會被攔截。 這意味著自呼叫實際上是目標物件中的一個方法呼叫目標物件的另一個方法,即使被呼叫的方法用@Transactional 標記,也不會在執行時導致實際事務。 此外,代理必須完全初始化以提供預期的行為,因此您不應在初始化程式碼中依賴此功能,即@PostConstruct。

2.3 事務的隔離級別-Isolation

public enum Isolation {
	DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
	READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED), 
	READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED), 
	REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ), 
	SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);

	private final int value;

	Isolation(int value) {
		this.value = value;
	}

	public int value() {
		return this.value;
	}
}

Spring事務隔離級別共有5種,隔離級別的設定依賴當前資料庫是否支援。

  • DEFAULT:使用當前資料庫的預設隔離級別。例如Oracle是READ_COMMITED,MySQL是READ_REPEATED。
  • READ_UNCOMMITED:可導致髒讀、不可重複讀、幻讀。
  • READ_COMMITTED:阻止髒讀,可導致不可重複讀、幻讀。
  • REPEATABLE_READ:阻止髒讀和不可重複讀,可導致幻讀。
  • SERIALIZABLE:該級別下事務順序執行,阻止上面的缺陷,開銷很大。

3 SpringBoot啟動類為什麼不需要加@EnableTransactionManagement註解

org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration

SpringBoot載入spring.factories時,會載入事務配置類TransactionAutoConfiguration,內部有開啟事務管理的配置。

// ~TransactionAutoConfiguration中的內部類
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(TransactionManager.class)
@ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class)
public static class EnableTransactionManagementConfiguration {

	@Configuration(proxyBeanMethods = false)
	@EnableTransactionManagement(proxyTargetClass = false)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
			matchIfMissing = false)
	public static class JdkDynamicAutoProxyConfiguration {

	}

	@Configuration(proxyBeanMethods = false)
	@EnableTransactionManagement(proxyTargetClass = true)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
			matchIfMissing = true)
	public static class CglibAutoProxyConfiguration {

	}
}

4 Spring宣告式事務的內部實現機制

image

在應用系統呼叫宣告@Transactional 的目標方法時,Spring Framework 預設使用 AOP 代理,結合AOP和事務後設資料(註解)在程式碼執行時生成一個代理物件,根據@Transactional 的屬性配置資訊,這個代理物件決定該宣告@Transactional 的目標方法是否由攔截器 TransactionInterceptor 來使用攔截,在 TransactionInterceptor 攔截時,會在目標方法開始執行之前建立並加入事務,並執行目標方法的邏輯, 最後根據執行情況是否出現異常,利用抽象事務管理器AbstractPlatformTransactionManager 運算元據源 DataSource 提交或回滾事務。

image

事務管理的框架是由抽象事務管理器 AbstractPlatformTransactionManager 來提供的,而具體的底層事務處理實現由 PlatformTransactionManager 的具體實現類來實現,如事務管理器 DataSourceTransactionManager。不同的事務管理器管理不同的資料資源 DataSource,比如 DataSourceTransactionManager 管理 JDBC 的 Connection。

img

參考文件

  1. https://www.cnblogs.com/xd502djj/p/10940627.html
  2. https://docs.spring.io/spring/docs/4.3.13.RELEASE/spring-framework-reference/htmlsingle/#transaction-declarative-annotations

相關文章