1. 場景與要解決的問題
在業務代碼里,常見訴求是:只有當數據庫事務真正提交成功后,才去執行某些“后置動作”,例如:
發送 MQ、推送消息、寫審計/埋點日志、刷新緩存、通知外部系統等。
如果這些動作在事務提交前就執行,一旦事務最后回滾,就會出現數據與副作用不一致(例如:數據庫沒落庫,但 MQ 已發出)。
Spring 給出兩類工具來“跟隨事務走”:
TransactionSynchronization
:直接注冊事務回調,在afterCommit()
等時點執行。@TransactionalEventListener
:發布事件 + 事務階段監聽,在AFTER_COMMIT
等階段觸發監聽方法。
2. TransactionSynchronization
使用方法
2.1 它是什么
事務同步回調接口(Transaction Synchronization Callback Interface)。
它能讓你在事務的關鍵節點(提交前、提交后、回滾后、完成后)掛接同步邏輯。
本質是 “鉤子/回調”,屬于 Spring 事務 SPI(Service Provider Interface)擴展點。
2.2 使用方法
@Service
public class WithdrawService {@Transactional(rollbackFor = Exception.class)public void createWithdrawOrder(Long userId, BigDecimal amount) {// 1) 業務數據更新(示例)// - 扣可用余額、加凍結余額、插入訂單等// - 此處略…Long orderId = 123L; // 假設是插入訂單后拿到的IDBigDecimal income = amount;// 2) 綁定到“當前事務”的提交后回調TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {@Overridepublic void afterCommit() {try {// 3) 事務真正提交成功后才會執行到這里withdrawProducer.sendMessage(orderId);log.info("已發送提現MQ, orderId={}", orderId);} catch (Exception ex) {// 注意:此時事務已提交,失敗不會回滾主事務log.error("提交后發送MQ失敗, orderId={}", orderId, ex);// 可在此觸發重試/告警/記錄Outbox補償等}}});}
}
2.3 最佳實踐
必須在活動事務中注冊:可用
TransactionSynchronizationManager.isSynchronizationActive()
檢查。不要在
afterCommit
里做重 IO/耗時操作,以免拉長請求尾延遲;建議再丟到自定義線程池執行。異常處理:
afterCommit
里異常不會回滾主事務(已提交),要自處置(告警/重試/Outbox)。事務傳播:在哪一層注冊,就跟隨那一層的事務。若內部有
REQUIRES_NEW
子事務,你在子事務里注冊的回調只跟隨子事務。多層回調:如果需要控制多個回調的順序,可讓回調實現
org.springframework.core.Ordered
接口,getOrder()
返回值越小優先級越高。
3. @TransactionalEventListener
使用方法
3.1 它是什么
發布-訂閱風格的事務階段監聽。業務方法里發布事件,監聽方法用
@TransactionalEventListener
聲明在指定事務階段運行(常用AFTER_COMMIT
)。事件可以被多個監聽器消費;可配
@Async
在提交后異步執行。
3.2 使用方法(同步監聽模板)
// 1) 自定義事件(POJO 即可)
public record WithdrawOrderCreatedEvent(Long orderId, Long userId, BigDecimal amount) {}// 2) 事務中發布事件
@Service
public class WithdrawService {@Autowired private ApplicationEventPublisher publisher;@Transactional(rollbackFor = Exception.class)public void createWithdrawOrder(Long userId, BigDecimal amount) {// 業務入庫…(略)Long orderId = 123L;publisher.publishEvent(new WithdrawOrderCreatedEvent(orderId, userId, amount));}
}// 3) 監聽端:僅在“提交成功后”觸發
@Component
public class WithdrawEventListener {@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)public void onCreated(WithdrawOrderCreatedEvent evt) {try {withdrawProducer.sendMessage(evt.orderId());log.info("提交后發送MQ成功, orderId={}", evt.orderId());} catch (Exception ex) {log.error("提交后發送MQ失敗, orderId={}", evt.orderId(), ex);}}
}
3.3 提交后異步執行(可選)
@Configuration
@EnableAsync
public class AsyncCfg implements AsyncConfigurer {@Override public Executor getAsyncExecutor() {return Executors.newFixedThreadPool(8);}
}@Component
public class WithdrawAsyncListener {@Async@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)public void onCreated(WithdrawOrderCreatedEvent evt) {withdrawProducer.sendMessage(evt.orderId()); // 提交后異步發送}
}
4. 對比與選型
一致性保障
二者都能保證:只有事務提交成功后才執行(
afterCommit
/AFTER_COMMIT
)。
耦合與擴展
TransactionSynchronization
:代碼直接注冊回調,與業務方法耦合,適合單一后置動作。@TransactionalEventListener
:發布-訂閱,天然解耦,一個事件可被多個監聽器消費,易擴展。
異步能力
TransactionSynchronization
:天生同步;可在回調內手動丟線程池異步。@TransactionalEventListener
:可直接疊加@Async
在提交后異步執行。
無事務時的行為
TransactionSynchronization
:必須存在活動事務,否則注冊失敗。@TransactionalEventListener
:默認無事務不觸發;fallbackExecution = true
可強制執行(會失去“提交后”語義)。
性能與代碼復雜度
TransactionSynchronization
:路徑最短、開銷最小、代碼最少。@TransactionalEventListener
:有事件派發的輕微開銷,換來更好的解耦與可維護性。
測試與團隊協作
TransactionSynchronization
:更貼近底層鉤子,單一動作簡單直觀。@TransactionalEventListener
:語義清晰(業務事件),多人協作與模塊解耦更友好,監聽可獨立單測。
推薦選型(面向常見場景)
只需一個消費方,追求超輕量 → 選
TransactionSynchronization.afterCommit()
。需要解耦/多個消費方/可異步 → 選
@TransactionalEventListener(phase = AFTER_COMMIT)
。需要強可靠(不能丟消息) → 在以上任一方案外,疊加 Outbox 模式(業務表 + 出站表同事務寫入,后臺可靠投遞 MQ/補償重試)。