一、前言
在現代軟件開發中,事務處理是必不可少的一部分。當多個操作需要作為一個整體來執行時,事務可以確保數據的完整性和一致性,并避免出現異常和錯誤情況。在SpringBoot框架中,我們可以使用聲明式事務和編程式事務來管理事務處理。其中事務的坑也是不少,比較常見的就是事務失效,大家可以看看!后面小編在出一篇事務失效場景哈,喜歡的可以關注,等待更新哈!
這篇博客將重點探討這兩種事務處理方式的源碼實現、區別、優缺點、適用場景以及實戰。我們來接著說事務,里面還涉及到三個知識點,大家可以自行百度好好了解!
- 事務的特性
- 事務的傳播行為
- 隔離級別
本篇文章主要講的就是實現事務的兩種方式的分析!
讓我們開始探索聲明式事務和編程式事務吧!
文章很長,耐心看完希望對你有幫助!
本文源碼是使用:springboot2.7.1。
二、開啟使用和大致源碼實現
1、開啟使用
我們在啟動類上添加注解:@EnableTransactionManagement。
后續使用就可以添加注解@Transactional(rollbackFor = Exception.class)使用,或者是使用編程式事務使用了 !
后面我們在詳細演示怎么使用哈!
2、聲明式事務源碼
public class TransactionInterceptor extends TransactionAspectSupport
implements MethodInterceptor, Serializable{}
TransactionInterceptor UML圖:
聲明式事務主要是通過AOP實現,主要包括以下幾個節點:
- 啟動時掃描@Transactional注解:在啟動時,Spring Boot會掃描所有使用了@Transactional注解的方法,并將其封裝成TransactionAnnotationParser對象。
- AOP 來實現事務管理的核心類依然是 TransactionInterceptor。TransactionInterceptor 是一個攔截器,用于攔截使用了 @Transactional 注解的方法
- 將TransactionInterceptor織入到目標方法中:在AOP編程中,使用AspectJ編寫切面類,通過@Around注解將TransactionInterceptor織入到目標方法中。
- 在目標方法執行前創建事務:在目標方法執行前,TransactionInterceptor會調用PlatformTransactionManager創建一個新的事務,并將其納入到當前線程的事務上下文中。
- 執行目標方法:在目標方法執行時,如果發生異常,則將事務狀態標記為ROLLBACK_ONLY;否則,將事務狀態標記為COMMIT。
- 提交或回滾事務:在目標方法執行完成后,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 = determ.NETransactionManager(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;
}
}
三、兩者區別
上面說了源碼里的大體實現,下面我們來介紹一下兩者區別:
- 技術實現方式:聲明式事務是通過AOP技術來實現的,而編程式事務是通過編寫具體的代碼來實現的。
- 代碼耦合度:聲明式事務可以將事務處理邏輯從業務代碼中分離出來,從而降低代碼的耦合度。而編程式事務需要在業務代碼中顯式地調用事務管理代碼,因此會增加代碼的耦合度。
- 難易程度:聲明式事務相對來說比較容易上手,開發人員只需要學習注解或XML配置即可。而編程式事務需要開發人員理解事務管理的底層機制,并編寫具體的代碼。
- 性能影響:由于聲明式事務是由容器來處理的,所以在一些場景下可能會對性能產生影響,大事務會有很多問題(下面在說一下大事務出現的問題)。而編程式事務由于直接調用事務管理API,相對來說會有更好的性能表現。
總體而言,聲明式事務和編程式事務都有各自的優缺點,開發人員需要根據具體需求選擇適合的方式來控制事務。
補充:
大事務時間過長可能會導致以下問題:
數據庫鎖定:當事務涉及到大量的數據操作時,事務可能會占用數據庫資源并長時間鎖定相關數據。這可能會導致其他事務無法訪問或修改這些數據,從而降低系統的并發性能和吞吐量。
資源耗盡:長時間運行的事務需要占用更多的系統資源,如內存和CPU等。如果系統資源不足,可能會導致系統出現延遲、死鎖等問題,甚至導致系統崩潰。
事務失敗概率增加:當事務時間過長時,事務執行期間可能會發生各種錯誤,如網絡故障、硬件故障、操作系統問題等。此時,事務可能無法成功提交,導致數據丟失或數據不一致。
應用程序超時:應用程序通常會為每個事務設置一個超時時間,以避免事務持續時間過長。如果事務持續時間超過設定的超時時間,則應用程序可能會因為等待事務完成而阻塞,最終導致應用程序崩潰或超時。
回滾時間增加:如果事務失敗需要回滾,長時間運行的事務將需要更長的時間來進行回滾操作。這可能會導致數據不一致或丟失,并增加數據庫維護的工作量。因此,開發人員應該盡量避免事務時間過長,合理地設置事務范圍、優化事務操作方式以及減少數據訪問次數等措施,以提高系統的并發性能和吞吐量。
方案:
大事務可以拆分小的事務,一下查詢方面的可以提取出來,操作數據庫的抽離出來專門加上事務。也可以使用CompletableFuture組合式異步編排來解決大事務的問題!
四、優缺點
1、聲明式事務
聲明式事務通常通過AOP技術實現,在方法或類級別上聲明事務屬性。聲明式事務的優點包括:
簡化代碼:開發人員只需要關注業務邏輯,而無需手動管理事務,可以減少代碼復雜度和工作量。
可配置性強:事務屬性可以通過XML文件、注解等方式進行配置,靈活方便。
易于擴展:可以通過AOP技術輕松地擴展使其支持新的事務策略。
聲明式事務存在以下缺點:
限制較大:事務屬性需要在方法或類級別進行聲明,這可能會導致某些情況下難以滿足特定的業務需求。
難以調試:由于事務是在AOP層面進行管理的,因此在調試時可能難以追蹤事務管理的具體細節。
2、編程式事務
編程式事務通常通過API接口實現,開發人員可以在代碼中顯式地管理事務。
編程式事務的優點包括:
靈活性強:開發人員可以在代碼中根據具體業務需要來控制事務的具體范圍和屬性。
易于調試:由于事務管理在代碼層面上實現,因此開發人員可以很容易地追蹤事務管理的細節。
編程式事務存在以下缺點:
代碼復雜度高:需要在代碼中手動處理事務,并處理各種異常情況,可能會增加代碼的復雜度和工作量。
可配置性差:事務的范圍和屬性需要在代碼中顯式聲明,這可能會導致一些特定的業務需求難以滿足。
總之,聲明式事務和編程式事務各有優缺點。開發人員需要根據具體業務需求和場景選擇使用合適的事務管理方式。
五、使用場景
聲明式事務通常適用于以下場景:
- 大型企業級應用程序,需要管理多個事務。
- 代碼結構比較復雜,使用聲明式事務可以更好地管理和維護代碼(大事務參考上方的方案)。
- 聲明式事務可以將事務管理與業務邏輯分離,從而使得應用程序更加松耦合。
而編程式事務通常適用于以下場景:
- 需要更精確地控制事務的范圍和處理邏輯。
- 編程式事務通常比聲明式事務更加靈活,可以根據業務邏輯的需要來自定義事務的范圍、隔離級別以及回滾機制等。
- 在某些高并發場景下,可以使用編程式事務僅針對需要操作的數據進行鎖定,而不是對整個業務邏輯加事務。
在實際場景中,可以根據需求綜合考慮使用聲明式事務和編程式事務的優勢來進行選擇。
根據不同的用戶量來具體選擇,在幾乎沒有并發量的系統設計一條異步編排反而大材小用,可能造成資源的浪費;但是有需要等待遠程API的響應時,使用異步編排可以將等待時間最小化,并使得應用程序不必阻塞等待API響應,從而提高用戶體驗。
很多事情沒有絕對化,只有相對化,只要能支持現有正常的使用,不管什么樣的設計都是沒問題的! 可能好的設計會使系統在經受并發量增大的過程中無感,還是要調研清楚,從而設計出更好的方案,防止資源浪費!
盡管小編還沒有什么架構經驗,但還是對架構充滿興趣,不想做架構師的開發不是好開發哈??!當然你也可以走管理?。?/p>
六、實戰
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框架中的聲明式事務和編程式事務,并分析了它們的源碼實現、區別、優缺點、適用場景以及實戰。
無論是采用哪種方式來管理事務,都需要考慮到業務需求和開發團隊的實際情況,選擇合適的事務處理方式,以確保系統的可靠性和穩定性。
希望通過本文的介紹,你能夠更好地理解聲明式事務和編程式事務的概念和原理,在開發過程中選擇合適的事務處理方式,提高項目的可維護性和穩定性。