Spring Framework 提供依賴注入容器,以及面向 AOP、資料存取、交易、訊息傳遞和 Web 應用程式的專用模組。交易管理展現了該框架的整合模型:應用程式程式碼宣告邊界,而攔截器則協調特定資源的 PlatformTransactionManager 或反應式交易管理器。
基於代理的宣告式交易 #
在預設的 advice 模式下,Spring 會使用 AOP 代理包裝符合條件的 Bean。進入符合 @Transactional 的方法之呼叫會經過 TransactionInterceptor,由它讀取交易中繼資料、啟動交易或加入既有交易、呼叫目標物件,然後提交或回復。
@Service
class OrderService {
private final OrderRepository orders;
private final PaymentGateway payments;
@Transactional
public Order place(CreateOrder command) {
Order order = orders.insert(command);
payments.reserve(order);
return order;
}
}代理存在實務上的限制。呼叫必須經由代理進入,而且方法必須能由所選的代理機制攔截。以介面為基礎的 JDK 代理會公開介面方法;以類別為基礎的代理會繼承目標類別,因此無法對 final 方法套用 advice。
自我呼叫 #
this.reprice() 這類呼叫會停留在目標物件內部,不會重新進入其代理。因此,若只能透過自我呼叫到達 reprice(),該方法上的交易中繼資料會被忽略:
public void importOrders(List<Order> batch) {
batch.forEach(this::storeOne); // 略過 storeOne 上的交易 advice
}如果交易操作代表實際的邊界,請將它移至另一個注入式服務。程式化交易管理或 AspectJ weaving 也可作為替代方案;注入 Bean 自身的代理會讓領域程式碼與容器機制耦合,並增加測試難度。
邊界粒度 #
一筆交易應包含一個應用程式層級的一致性操作。控制器層級的邊界往往會包含序列化和不相關的網路工作,而儲存庫層級的邊界可能將一個業務操作拆分成多次提交。服務方法通常是實用的單位,因為它能圍繞領域不變量協調多個儲存庫。
在分析鎖定持續時間、重試和失敗復原之前,不要讓資料庫交易跨越不可控的遠端呼叫而持續開啟。本機交易無法使外部服務具備原子性;可能需要 outbox、冪等性或 Saga 風格的協調機制。
傳播、隔離與回復 #
傳播定義方法與既有交易之間的關係:
REQUIRED是預設值,會加入既有交易,或建立新的交易。REQUIRES_NEW會暫停既有交易並啟動獨立交易,因此可能需要另一個連線池連線。SUPPORTS在存在交易時加入,否則不使用交易執行。MANDATORY要求存在既有交易;NEVER禁止交易存在。NOT_SUPPORTED會暫停既有交易。- 當交易管理器和資源支援儲存點時,
NESTED會在一個實體交易中使用儲存點。
隔離層級要求會對應到資源的能力。DEFAULT 使用其設定的預設值;其他值對應標準 JDBC 隔離層級。宣告某個隔離層級並不代表每個資料庫都實作相同的鎖定或快照語意,因此應針對選定的資料庫和驅動程式驗證行為。
預設情況下,Spring 會在 RuntimeException 和 Error 上將交易標記為回復,但不會對受檢例外執行此操作。當例外分類的語意不同時,請設定 rollbackFor 或 noRollbackFor。如果內部參與者將共用交易標記為僅回復,外層看似提交的操作可能會拋出 UnexpectedRollbackException。
Advisor 順序 #
安全性、重試、快取、指標、鎖定和交易等關注點都可以包裝相同的呼叫。巢狀順序會改變語意:在同一筆交易內重試,並不等同於每次嘗試都開啟新的交易;在授權之前執行快取也可能暴露資料。在支援的情況下使用 Ordered/@Order 指定順序,並測試最終的呼叫順序;當所需順序不明確時,避免堆疊註解。
宣告式代理的替代方案 #
根據所需的邊界,選擇以下三種方法之一:
- 將操作重構至另一個服務,並保留宣告式 advice。
- 使用
TransactionTemplate建立明確的本機區塊,使控制流程與例外處理清楚可見。 - 當透過 weaving 實作自我呼叫 advice 是有意採用的全系統選擇時,啟用
@TransactionalAspectJ 模式。
Order saved = transactionTemplate.execute(status -> orders.insert(command));
評論
在 GitHub 上閱讀或評論(發表評論需要登入)