Spring系列之事務及@Transactional

概要

Spring事務基於數據庫事務,JDBC事務過程:

  1. 獲取連接Connection con = DriverManager.getConnection()
  2. 開啓事務con.setAutoCommit(true/false);
  3. 執行CRUD
  4. 提交事務/回滾事務con.commit(), con.rollback();
  5. 關閉連接conn.close();

Spring事務主要分爲兩種:

  1. 編程式事務
  2. 聲明式事務

編程式事務

try {
    // something
    transactionManager.commit();
} catch (Exception e) {
    transactionManager.rollback();
    throw new RuntimeException("失敗");
}

顯而易見,代碼侵入性比較強,代碼冗餘。

聲明式事務

使用Spring聲明式事務,可以不再寫步驟2和4的代碼。基於AOP,有兩種實現方式:基於TX和AOP的xml配置文件方式、基於@Transactional註解的形式。配置文件開啓註解驅動,在類和方法上通過註解@Transactional標識。Spring在啓動時會去解析並生成相關的bean,爲這些類和方法生成代理,並根據@Transaction的相關參數進行相關配置注入,這樣就在代理中把相關的事務處理掉(開啓正常提交事務,異常回滾事務)。真正的數據庫層的事務提交和回滾是通過binlog或redo log實現。

Transactional

@Transactional可作用在接口、類、類方法。

  • 類:表示該類所有的public方法都配置相同的事務屬性信息
  • 方法:方法的事務優先級更高,會覆蓋類的事務配置信息
  • 接口:不推薦這種使用方法,因爲一旦標註在Interface上並且配置Spring AOP使用CGLib動態代理,將會導致@Transactional註解失效

屬性

@Transactional註解有哪些屬性?

  1. propagation,事務的傳播行爲,包括7種枚舉值;
  2. isolation,事務的隔離級別,枚舉值有5個,Isolation.DEFAULT:默認值,即使用底層數據庫的隔離級別,其他四種即爲MySQL的四種隔離級別。源碼在DataSourceUtils:
// Apply specific isolation level, if any.
Integer previousIsolationLevel = null;
if (definition != null && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
	int currentIsolation = con.getTransactionIsolation();
	if (currentIsolation != definition.getIsolationLevel()) {
		previousIsolationLevel = currentIsolation;
		con.setTransactionIsolation(definition.getIsolationLevel());
	}
}
  1. timeout,事務超時時間,默認值爲 -1。如果超過該時間限制但事務還沒有完成,則自動回滾事務
  2. readOnly,指定事務是否爲只讀事務,默認值爲 false;爲了忽略那些不需要事務的方法,比如讀取數據,可以設置 read-only 爲 true
  3. rollbackFor,指定觸發事務回滾的異常類型,可指定多個異常類型
  4. noRollbackFor,指定不觸發事務回滾的異常類型,可指定多個異常類型
  5. transactionManager,事務管理器,用於配置有多數據源,即多事務管理器情況下具體指定某一個事務管理器

事務傳播性

事務的傳播性一般在事務嵌套時候使用,比如在事務A裏面調用另外一個使用事務的方法,那麼這倆個事務是各自作爲獨立的事務執行提交,還是內層的事務合併到外層的事務一塊提交那,這就是事務傳播性要確定的問題。

spring事務的傳播屬性,就是多個事務同時存在時,spring應該如何處理這些事務的行爲。亦多個事務方法相互調用時,事務如何在這些方法間傳播。這些屬性在TransactionDefinition中定義:

  • Propagation.REQUIRED:默認值。如果當前存在事務,則加入該事務,如果不存在,則創建一個新的事務。(也就是說如果A方法和B方法都添加註解,在默認傳播模式下,A方法內部調用B方法,會把兩個方法的事務合併爲一個事務 )
  • Propagation.SUPPORTS:如果當前存在事務,則加入該事務;如果當前不存在事務,則以非事務的方式繼續運行
  • Propagation.MANDATORY:如果當前存在事務,則加入該事務;如果當前不存在事務,則拋出異常
  • Propagation.REQUIRES_NEW:重新創建一個新的事務,如果當前存在事務,暫停當前的事務。新建的事務將和被掛起的事務沒有任何關係,是兩個獨立的事務,外層事務失敗回滾之後,不能回滾內層事務執行的結果,內層事務失敗拋出異常,外層事務捕獲,也可以不處理回滾操作。(當類A中的a方法用默認Propagation.REQUIRED模式,類B中的b方法加上採用Propagation.REQUIRES_NEW模式,然後在a方法中調用b方法操作數據庫,然而a方法拋出異常後,b方法並沒有進行回滾,因爲Propagation.REQUIRES_NEW會暫停a方法的事務)
  • Propagation.NOT_SUPPORTED:以非事務的方式運行,如果當前存在事務,暫停當前的事務
  • Propagation.NEVER:以非事務的方式運行,如果當前存在事務,則拋出異常
  • Propagation.NESTED :如果一個活動的事務存在,則運行在一個嵌套的事務中。如果沒有活動事務,則按REQUIRED屬性執行。它使用一個單獨的事務,這個事務擁有多個可以回滾的保存點。內部事務的回滾不會對外部事務造成影響。它只對DataSourceTransactionManager事務管理器起效

readOnly

指定事務是否爲只讀事務,默認值爲 false。設置爲TRUE,需要底層數據庫支持。
MySQL來說,有兩種提交模式:

  • SET AUTOCOMMIT=0:禁止自動提交
  • SET AUTOCOMMIT=1:開啓自動提交

若開啓事務,AUTOCOMMIT要爲false,Spring源碼處理:

con.setAutoCommit(false);
// 只讀事務
if (isEnforceReadOnly() && definition.isReadOnly()) {
	try (Statement stmt = con.createStatement()) {
        stmt.executeUpdate("SET TRANSACTION READ ONLY");
    }
}

不加Transaction註解,默認是不開啓事務。單條查詢語句也沒有必要開啓事務,數據庫默認的配置就能滿足需求。但一次執行多條查詢語句,如統計查詢,報表查詢;此時多條查詢SQL必須保證整體的讀一致性,否則,在前條SQL查詢之後,後條SQL查詢之前,數據被其他用戶改變,就會造成數據的前後不一。需要開啓讀事務。

不生效

  1. 底層數據庫引擎不支持事務
    若數據庫引擎不支持事務,則Spring自然無法支持事務
  2. 當Spring開啓事務並設置傳播機制,覆蓋MySQL已有的事務隔離級別。如果MySQL不支持該隔離級別,Spring的事務就也不會生效。和第一點比較類似,但是不盡相同。三種配置將會導致事務失效:
Propagation.SUPPORTS
Propagation.NOT_SUPPORTED
Propagation.NEVER
  1. 非public的方法
    在非public方法上標記@Transactional,不報錯(可以通過編譯,但是IDE有警告),但沒有事務功能。因爲Transactional事務基於AOP,動態代理只能針對public方法進行代理。源碼如下:
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
    if (this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
        return null;
    }
}
  1. 異常被catch
    在整個事務的方法中使用try-catch,導致異常無法拋出,自然會導致事務失效。
@Transactional
public void method() {
	try {
		// transaction.commit();
	} catch (Exception ex) {
		return;
	}
}
  1. rollbackFor屬性設置錯誤
    指定異常觸發回滾,但是設置錯誤導致一些異常不能觸發回滾
  2. noRollbackFor屬性設置錯誤
    和rollbackFor類似。
  3. 方法中調用同類的方法
    最複雜的情況。

一個類中的A方法(無註解Transactional)在內部調用B方法(有註解Transactional),這樣會導致B方法中的事務失效。
失效原因:Spring在掃描Bean時會自動爲標註@Transactional註解的類生成一個代理類,當有註解的方法被調用的時候,實際上是代理類調用的,代理類在調用之前會開啓事務,執行事務的操作,但是同類中的方法互相調用,相當於this.B(),此時的B方法並非是代理類調用,而是直接通過原有的Bean直接調用,所以註解會失效。

a1方法是目標類A的原生方法,調用a1的時候即直接進入目標類A進行調用,在目標類A裏面只有a2的原生方法,在a1裏調用a2,即直接執行a2的原生方法,並不通過創建代理對象進行調用,所以並不會進入TransactionInterceptor的invoke方法,不會開啓事務。

原理

PlatformTransactionManager,基於AOP的類TransactionAspectSupport:
核心屬性:

private static final ThreadLocal<TransactionInfo> transactionInfoHolder = new NamedThreadLocal<>("Current aspect-driven transaction");

核心方法invokeWithinTransaction():


getTransaction():

@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
	// Use defaults if no transaction definition given.
	TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
	Object transaction = doGetTransaction();
	boolean debugEnabled = logger.isDebugEnabled();
	if (isExistingTransaction(transaction)) {
		// Existing transaction found -> check propagation behavior to find out how to behave.
		return handleExistingTransaction(def, transaction, debugEnabled);
	}
	// Check definition settings for new transaction.
	if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
		thrownew InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
	}
	// No existing transaction found -> check propagation behavior to find out how to proceed.
	if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
		thrownew IllegalTransactionStateException(
				"No existing transaction found for transaction marked with propagation 'mandatory'");
	}
	elseif (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
			def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
			def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
		SuspendedResourcesHolder suspendedResources = suspend(null);
		try {
			// 核心
			return startTransaction(def, transaction, debugEnabled, suspendedResources);
		}
		catch (RuntimeException | Error ex) {
			resume(null, suspendedResources);
			throw ex;
		}
	}
	else {
		// Create "empty" transaction: no actual transaction, but potentially synchronization.
		if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
			logger.warn("Custom isolation level specified but no actual transaction initiated; " +
					"isolation level will effectively be ignored: " + def);
		}
		boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
		return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
	}
}

事務攔截器
PlatformTransactionManager是Spring 中的事務管理接口,真正定義事務如何回滾和提交。
TransactionInterceptor,負責攔截方法執行,判斷是否需要提交或者回滾事務。
在這裏插入圖片描述

http://blog.leanote.com/post/medusar/Spring%E4%BA%8B%E5%8A%A1
https://www.iteye.com/topic/1122740

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章