Spring/SpringBoot中的宣告式事務和程式設計式事務原始碼、區別、優缺點、適用場景、實戰

2023-11-03 12:02:36

一、前言

在現代軟體開發中,事務處理是必不可少的一部分。當多個操作需要作為一個整體來執行時,事務可以確保資料的完整性和一致性,並避免出現異常和錯誤情況。在SpringBoot框架中,我們可以使用宣告式事務和程式設計式事務來管理事務處理。其中事務的坑也是不少,比較常見的就是事務失效,大家可以看看!後面小編在出一篇事務失效場景哈,喜歡的可以關注,等待更新哈!

這篇部落格將重點探討這兩種事務處理方式的原始碼實現、區別、優缺點、適用場景以及實戰。
我們來接著說事務,裡面還涉及到三個知識點,大家可以自行百度好好了解!

  • 事務的特性
  • 事務的傳播行為
  • 隔離級別

本篇文章主要講的就是實現事務的兩種方式的分析!

讓我們開始探索宣告式事務和程式設計式事務吧!

文章很長,耐心看完希望對你有幫助!

本文原始碼是使用:springboot2.7.1

二、開啟使用和大致原始碼實現

1. 開啟使用

我們在啟動類上新增註解:@EnableTransactionManagement

後續使用就可以新增註解@Transactional(rollbackFor = Exception.class)使用,或者是使用程式設計式事務使用了 !
後面我們在詳細演示怎麼使用哈!

2. 宣告式事務原始碼

public class TransactionInterceptor extends TransactionAspectSupport 
	implements MethodInterceptor, Serializable{}

TransactionInterceptor UML圖:

宣告式事務主要是通過AOP實現,主要包括以下幾個節點:

  1. 啟動時掃描@Transactional註解:在啟動時,Spring Boot會掃描所有使用了@Transactional註解的方法,並將其封裝成TransactionAnnotationParser物件。

  2. AOP 來實現事務管理的核心類依然是 TransactionInterceptor。TransactionInterceptor 是一個攔截器,用於攔截使用了 @Transactional 註解的方法

  3. 將TransactionInterceptor織入到目標方法中:在AOP程式設計中,使用AspectJ編寫切面類,通過@Around註解將TransactionInterceptor織入到目標方法中

  4. 在目標方法執行前建立事務:在目標方法執行前,TransactionInterceptor會呼叫PlatformTransactionManager建立一個新的事務,並將其納入到當前執行緒的事務上下文中。

  5. 執行目標方法:在目標方法執行時,如果發生異常,則將事務狀態標記為ROLLBACK_ONLY;否則,將事務狀態標記為COMMIT

  6. 提交或回滾事務:在目標方法執行完成後,TransactionInterceptor會根據事務狀態(COMMIT或ROLLBACK_ONLY)來決定是否提交或回滾事務。

原始碼:

@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
	// Work out the target class: may be {@code null}.
	// The TransactionAttributeSource should be passed the target class
	// as well as the method, which may be from an interface.
	Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

	// Adapt to TransactionAspectSupport's invokeWithinTransaction...
	return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
		@Override
		@Nullable
		public Object proceedWithInvocation() throws Throwable {
			return invocation.proceed();
		}
		@Override
		public Object getTarget() {
			return invocation.getThis();
		}
		@Override
		public Object[] getArguments() {
			return invocation.getArguments();
		}
	});
}

下面是核心處理方法,把不太重要的程式碼忽略了,留下每一步的節點。

@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
	final InvocationCallback invocation) throws Throwable {
	// 獲取事務屬性
	final TransactionManager tm = determineTransactionManager(txAttr);
	// 準備事務
	TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);
	// 執行目標方法
	Object retVal = invocation.proceedWithInvocation();
	 // 回滾事務
	completeTransactionAfterThrowing(txInfo, ex);
	// 提交事務
	commitTransactionAfterReturning(txInfo);
}		

3. 程式設計式事務原始碼

程式設計式事務主要下面的程式碼:

public class TransactionTemplate extends DefaultTransactionDefinition
	implements TransactionOperations, InitializingBean{}

TransactionTemplate UML圖:

TransactionTemplate類的execute()方法封裝了事務的具體實現,通過呼叫TransactionCallback物件的doInTransaction()方法來執行業務邏輯並管理事務。在具體實現中,TransactionTemplate類會自動控制事務的提交和回滾,並將異常丟擲給上層呼叫者進行處理。

@Override
@Nullable
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
	Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");

	if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
		return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
	}
	else {
		TransactionStatus status = this.transactionManager.getTransaction(this);
		T result;
		try {
			result = action.doInTransaction(status);
		}
		catch (RuntimeException | Error ex) {
			// Transactional code threw application exception -> rollback
			rollbackOnException(status, ex);
			throw ex;
		}
		catch (Throwable ex) {
			// Transactional code threw unexpected exception -> rollback
			rollbackOnException(status, ex);
			throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
		}
		this.transactionManager.commit(status);
		return result;
	}
}

三、兩者區別

上面說了原始碼裡的大體實現,下面我們來介紹一下兩者區別:

  1. 技術實現方式:宣告式事務是通過AOP技術來實現的,而程式設計式事務是通過編寫具體的程式碼來實現的。
  2. 程式碼耦合度:宣告式事務可以將事務處理邏輯從業務程式碼中分離出來,從而降低程式碼的耦合度。而程式設計式事務需要在業務程式碼中顯式地呼叫事務管理程式碼,因此會增加程式碼的耦合度。
  3. 難易程度:宣告式事務相對來說比較容易上手,開發人員只需要學習註解或XML設定即可。而程式設計式事務需要開發人員理解事務管理的底層機制,並編寫具體的程式碼。
  4. 效能影響:由於宣告式事務是由容器來處理的,所以在一些場景下可能會對效能產生影響,大事務會有很多問題(下面在說一下大事務出現的問題)。而程式設計式事務由於直接呼叫事務管理API,相對來說會有更好的效能表現。

總體而言,宣告式事務和程式設計式事務都有各自的優缺點,開發人員需要根據具體需求選擇適合的方式來控制事務。

補充:

大事務時間過長可能會導致以下問題:
資料庫鎖定:當事務涉及到大量的資料操作時,事務可能會佔用資料庫資源並長時間鎖定相關資料。這可能會導致其他事務無法存取或修改這些資料,從而降低系統的並行效能和吞吐量。
資源耗盡:長時間執行的事務需要佔用更多的系統資源,如記憶體和CPU等。如果系統資源不足,可能會導致系統出現延遲、死鎖等問題,甚至導致系統崩潰。
事務失敗概率增加:當事務時間過長時,事務執行期間可能會發生各種錯誤,如網路故障、硬體故障、作業系統問題等。此時,事務可能無法成功提交,導致資料丟失或資料不一致。
應用程式超時:應用程式通常會為每個事務設定一個超時時間,以避免事務持續時間過長。如果事務持續時間超過設定的超時時間,則應用程式可能會因為等待事務完成而阻塞,最終導致應用程式崩潰或超時。
回滾時間增加:如果事務失敗需要回滾,長時間執行的事務將需要更長的時間來進行回滾操作。這可能會導致資料不一致或丟失,並增加資料庫維護的工作量。
因此,開發人員應該儘量避免事務時間過長,合理地設定事務範圍、優化事務操作方式以及減少資料存取次數等措施,以提高系統的並行效能和吞吐量。

方案:
大事務可以拆分小的事務,一下查詢方面的可以提取出來,運算元據庫的抽離出來專門加上事務。
也可以使用CompletableFuture組合式非同步編排來解決大事務的問題!!

四、優缺點

1. 宣告式事務

宣告式事務通常通過AOP技術實現,在方法或類級別上宣告事務屬性。
宣告式事務的優點包括:

簡化程式碼:開發人員只需要關注業務邏輯,而無需手動管理事務,可以減少程式碼複雜度和工作量。
可設定性強:事務屬性可以通過XML檔案、註解等方式進行設定,靈活方便。
易於擴充套件:可以通過AOP技術輕鬆地擴充套件使其支援新的事務策略。

宣告式事務存在以下缺點:

限制較大:事務屬性需要在方法或類級別進行宣告,這可能會導致某些情況下難以滿足特定的業務需求。
難以偵錯:由於事務是在AOP層面進行管理的,因此在偵錯時可能難以追蹤事務管理的具體細節。

2. 程式設計式事務

程式設計式事務通常通過API介面實現,開發人員可以在程式碼中顯式地管理事務。
程式設計式事務的優點包括:

靈活性強:開發人員可以在程式碼中根據具體業務需要來控制事務的具體範圍和屬性。
易於偵錯:由於事務管理在程式碼層面上實現,因此開發人員可以很容易地追蹤事務管理的細節。

程式設計式事務存在以下缺點:

程式碼複雜度高:需要在程式碼中手動處理事務,並處理各種異常情況,可能會增加程式碼的複雜度和工作量。
可設定性差:事務的範圍和屬性需要在程式碼中顯式宣告,這可能會導致一些特定的業務需求難以滿足。

總之,宣告式事務和程式設計式事務各有優缺點。開發人員需要根據具體業務需求和場景選擇使用合適的事務管理方式。

五、使用場景

宣告式事務通常適用於以下場景:

  • 大型企業級應用程式,需要管理多個事務。
  • 程式碼結構比較複雜,使用宣告式事務可以更好地管理和維護程式碼(大事務參考上方的方案)。
  • 宣告式事務可以將事務管理與業務邏輯分離,從而使得應用程式更加鬆耦合。

而程式設計式事務通常適用於以下場景:

  • 需要更精確地控制事務的範圍和處理邏輯。
  • 程式設計式事務通常比宣告式事務更加靈活,可以根據業務邏輯的需要來自定義事務的範圍、隔離級別以及回滾機制等。
  • 在某些高並行場景下,可以使用程式設計式事務僅針對需要操作的資料進行鎖定,而不是對整個業務邏輯加事務。

在實際場景中,可以根據需求綜合考慮使用宣告式事務和程式設計式事務的優勢來進行選擇。

根據不同的使用者量來具體選擇,在幾乎沒有並行量的系統設計一條非同步編排反而大材小用,可能造成資源的浪費;但是有需要等待遠端API的響應時,使用非同步編排可以將等待時間最小化,並使得應用程式不必阻塞等待API響應,從而提高使用者體驗。

很多事情沒有絕對化,只有相對化,只要能支援現有正常的使用,不管什麼樣的設計都是沒問題的!
可能好的設計會使系統在經受並行量增大的過程中無感,還是要調研清楚,從而設計出更好的方案,防止資源浪費!

儘管小編還沒有什麼架構經驗,但還是對架構充滿興趣,不想做架構師的開發不是好開發哈!!當然你也可以走管理!!

六、實戰

1. 宣告式事務

這裡就簡單模擬一下,為了模擬報錯,把OperIp設定為唯一!

@Transactional(rollbackFor = Exception.class)大家經常使用,就不多演示了!

@Transactional(rollbackFor = Exception.class)
@Override
public void template() {
    SysLog sysLog = new SysLog();
    sysLog.setOperIp("123");
    SysLog sysLog1 = new SysLog();
    sysLog1.setOperIp("hhh");
    log.info("插入第一條資料開始========");
    testMapper.insert(sysLog);
    log.info("插入第一條資料完成========");
    log.info("插入第二條資料開始========");
    testMapper.insert(sysLog);
    log.info("插入第二條資料完成========");

}

此時資料沒有資料,全部回滾成功!

2. 程式設計式事務

首先注入TransactionTemplate

@Autowired
private TransactionTemplate transactionTemplate;

後面直接使用即可:

@Override
public void template() {
    SysLog sysLog = new SysLog();
    sysLog.setOperIp("123");
    SysLog sysLog1 = new SysLog();
    sysLog1.setOperIp("hhh");
    log.info("插入第一條資料開始========");
    testMapper.insert(sysLog);
    log.info("插入第一條資料完成========");

    transactionTemplate.execute(status -> {
        log.info("程式設計式事務中:插入第一條資料開始========");
        testMapper.insert(sysLog1);
        log.info("程式設計式事務中:插入第一條資料完成========");
        log.info("程式設計式事務中:插入第二條資料開始========");
        int insert = testMapper.insert(sysLog);
        log.info("程式設計式事務中:插入第二條資料完成========");
        return insert;
    });
}


檢視資料庫,第一條不在程式設計式事務內不會參與回滾!

七、總結

本文介紹了SpringBoot框架中的宣告式事務和程式設計式事務,並分析了它們的原始碼實現、區別、優缺點、適用場景以及實戰。
無論是採用哪種方式來管理事務,都需要考慮到業務需求和開發團隊的實際情況,選擇合適的事務處理方式,以確保系統的可靠性和穩定性。

希望通過本文的介紹,你能夠更好地理解宣告式事務和程式設計式事務的概念和原理,在開發過程中選擇合適的事務處理方式,提高專案的可維護性和穩定性。


如果對你有幫助,還請動一下您的發財小手,關注一下公眾號哈!!謝謝您的關注!!文章首發看!!!

建了一個IT交流群,歡迎大家加入,過期加我拉你們進哈!