Spring事務(wù)傳播機(jī)制最佳實(shí)踐
?? 導(dǎo)讀:在復(fù)雜的企業(yè)應(yīng)用中,事務(wù)管理是保證數(shù)據(jù)一致性的關(guān)鍵。當(dāng)多個(gè)事務(wù)方法相互調(diào)用時(shí),如何確保它們協(xié)同工作?Spring的事務(wù)傳播機(jī)制為我們提供了優(yōu)雅的解決方案。本文將帶您深入理解這一機(jī)制,掌握不同場景下的最佳實(shí)踐。
1. ?? 什么是事務(wù)傳播行為
?? 想象一下:你在銀行APP上進(jìn)行轉(zhuǎn)賬,這個(gè)過程涉及「扣款」和「入賬」兩個(gè)操作。如果扣款成功但入賬失敗,你的錢就會(huì)憑空消失!這就是為什么我們需要事務(wù) - 確保這兩個(gè)操作要么都成功,要么都失敗。
在Spring管理的事務(wù)中,當(dāng)一個(gè)事務(wù)方法被另一個(gè)事務(wù)方法調(diào)用時(shí),必須指定事務(wù)應(yīng)該如何傳播。例如,方法可能繼續(xù)在現(xiàn)有事務(wù)中運(yùn)行,也可能開啟一個(gè)新事務(wù),并在自己的事務(wù)中運(yùn)行。Spring定義了七種不同的傳播行為,用于控制事務(wù)的傳播方式。
事務(wù)傳播行為是Spring框架事務(wù)管理的核心概念之一,它決定了事務(wù)方法和事務(wù)方法發(fā)生嵌套調(diào)用時(shí)事務(wù)如何進(jìn)行傳播。簡單來說,它回答了這個(gè)問題:當(dāng)一個(gè)事務(wù)方法調(diào)用另一個(gè)事務(wù)方法時(shí),會(huì)發(fā)生什么?
2. ?? Spring支持的七種事務(wù)傳播行為
?? 類比理解:如果把事務(wù)比作一場戲劇表演,傳播行為就是決定演員(方法)如何上場的規(guī)則 - 是加入已有的表演,還是開啟一個(gè)全新的獨(dú)立演出,或者干脆拒絕參與?
2.1 ?? REQUIRED(默認(rèn))
?? 生活類比:就像加入一個(gè)已經(jīng)開始的家庭聚會(huì),如果聚會(huì)已經(jīng)在進(jìn)行,你就加入;如果還沒開始,你就負(fù)責(zé)組織一個(gè)新的聚會(huì)。
Propagation.REQUIRED
- 含義:如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則創(chuàng)建一個(gè)新的事務(wù)。
- 應(yīng)用場景:適用于絕大多數(shù)情況,是Spring默認(rèn)的傳播行為。
- 特點(diǎn):
- 當(dāng)A方法(REQUIRED)調(diào)用B方法(REQUIRED)時(shí),B方法會(huì)加入到A方法的事務(wù)中。
- 如果B方法發(fā)生異常,A方法和B方法都會(huì)回滾。
- 這是最常用的傳播行為,適合大多數(shù)業(yè)務(wù)場景。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 數(shù)據(jù)庫操作
methodB();
// 更多數(shù)據(jù)庫操作
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// 數(shù)據(jù)庫操作
}2.2 ?? SUPPORTS
?? 生活類比:就像一個(gè)隨和的朋友,別人組織活動(dòng)時(shí)會(huì)積極參與,沒人組織時(shí)也能獨(dú)自安靜地做自己的事。
Propagation.SUPPORTS
- 含義:如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則以非事務(wù)方式執(zhí)行。
- 應(yīng)用場景:適用于可以在事務(wù)內(nèi)也可以在事務(wù)外執(zhí)行的方法,比如一些查詢方法。
- 特點(diǎn):
- 當(dāng)A方法(有事務(wù))調(diào)用B方法(SUPPORTS)時(shí),B方法會(huì)加入到A方法的事務(wù)中。
- 當(dāng)A方法(無事務(wù))調(diào)用B方法(SUPPORTS)時(shí),B方法以非事務(wù)方式執(zhí)行。
- 非常適合只讀操作,如數(shù)據(jù)查詢,提高性能。
@Transactional
public void methodA() {
// 數(shù)據(jù)庫操作
methodB(); // B方法會(huì)在A的事務(wù)中執(zhí)行
}
// 非事務(wù)方法調(diào)用
public void methodC() {
methodB(); // B方法會(huì)以非事務(wù)方式執(zhí)行
}
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
// 數(shù)據(jù)庫操作
}2.3 ?? MANDATORY
?? 生活類比:就像一個(gè)嚴(yán)格的團(tuán)隊(duì)成員,只愿意在有組織的團(tuán)隊(duì)活動(dòng)中參與,如果沒有團(tuán)隊(duì)活動(dòng),就會(huì)直接拒絕并抗議。
Propagation.MANDATORY
- 含義:如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則拋出異常。
- 應(yīng)用場景:適用于必須在事務(wù)中執(zhí)行的方法,確保方法在事務(wù)環(huán)境中被調(diào)用。
- 特點(diǎn):
- 強(qiáng)制要求外部調(diào)用者提供事務(wù)環(huán)境。
- 如果沒有事務(wù)環(huán)境,則拋出
IllegalTransactionStateException異常。 - 用于確保關(guān)鍵業(yè)務(wù)操作必須在事務(wù)控制下執(zhí)行,提高安全性。
@Transactional
public void methodA() {
// 數(shù)據(jù)庫操作
methodB(); // 正常執(zhí)行,B方法加入A的事務(wù)
}
// 非事務(wù)方法調(diào)用
public void methodC() {
methodB(); // 拋出異常,因?yàn)闆]有事務(wù)環(huán)境
}
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
// 數(shù)據(jù)庫操作
}2.4 ?? REQUIRES_NEW
??? 生活類比:就像一個(gè)獨(dú)立的人,即使已經(jīng)在參加一個(gè)聚會(huì),也會(huì)暫時(shí)離開去做自己的事情,完成后再回到原來的聚會(huì)中。
Propagation.REQUIRES_NEW
- 含義:創(chuàng)建一個(gè)新的事務(wù),如果當(dāng)前存在事務(wù),則掛起當(dāng)前事務(wù)。
- 應(yīng)用場景:適用于需要獨(dú)立事務(wù)的方法,不受外部事務(wù)影響。
- 特點(diǎn):
- 總是啟動(dòng)一個(gè)新的事務(wù)。
- 如果當(dāng)前存在事務(wù),則將當(dāng)前事務(wù)掛起。
- 內(nèi)部事務(wù)與外部事務(wù)相互獨(dú)立,互不影響。
- 適合記錄日志、發(fā)送通知等不應(yīng)受主事務(wù)影響的操作。
@Transactional
public void methodA() {
// 數(shù)據(jù)庫操作 - 事務(wù)A
try {
methodB(); // 執(zhí)行新事務(wù)B,事務(wù)A被掛起
} catch (Exception e) {
// 即使B事務(wù)回滾,A事務(wù)不受影響
}
// 繼續(xù)事務(wù)A的操作
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// 數(shù)據(jù)庫操作 - 在新的事務(wù)B中
// 如果這里拋出異常,只有事務(wù)B回滾,事務(wù)A不受影響
}2.5 ?? NOT_SUPPORTED
?? 生活類比:就像一個(gè)需要安靜環(huán)境的讀書人,即使周圍有熱鬧的聚會(huì),也會(huì)找一個(gè)安靜的角落獨(dú)自閱讀,不受外界干擾。
Propagation.NOT_SUPPORTED
- 含義:以非事務(wù)方式執(zhí)行操作,如果當(dāng)前存在事務(wù),則掛起當(dāng)前事務(wù)。
- 應(yīng)用場景:適用于不需要事務(wù)的操作,特別是一些耗時(shí)的只讀操作。
- 特點(diǎn):
- 總是以非事務(wù)方式執(zhí)行。
- 如果當(dāng)前存在事務(wù),則將當(dāng)前事務(wù)掛起。
- 適合執(zhí)行耗時(shí)的查詢操作,避免長時(shí)間占用數(shù)據(jù)庫連接。
@Transactional
public void methodA() {
// 數(shù)據(jù)庫操作 - 事務(wù)A
methodB(); // 調(diào)用B方法,事務(wù)A被掛起
// 繼續(xù)事務(wù)A的操作
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void methodB() {
// 數(shù)據(jù)庫操作 - 非事務(wù)執(zhí)行
// 這里的操作不會(huì)影響事務(wù)A
}2.6 ?? NEVER
??? 生活類比:就像一個(gè)堅(jiān)持獨(dú)處的隱士,不僅自己不參加任何社交活動(dòng),而且如果有人試圖把他拉進(jìn)社交圈,他會(huì)立刻表示強(qiáng)烈抗議。
Propagation.NEVER
- 含義:以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則拋出異常。
- 應(yīng)用場景:適用于必須在非事務(wù)環(huán)境下執(zhí)行的操作。
- 特點(diǎn):
- 強(qiáng)制要求非事務(wù)環(huán)境。
- 如果當(dāng)前存在事務(wù),則拋出
IllegalTransactionStateException異常。 - 用于確保某些操作絕對不在事務(wù)中執(zhí)行,如某些特殊的查詢或統(tǒng)計(jì)操作。
// 非事務(wù)方法調(diào)用
public void methodC() {
methodB(); // 正常執(zhí)行,因?yàn)闆]有事務(wù)環(huán)境
}
@Transactional
public void methodA() {
// 數(shù)據(jù)庫操作
methodB(); // 拋出異常,因?yàn)榇嬖谑聞?wù)環(huán)境
}
@Transactional(propagation = Propagation.NEVER)
public void methodB() {
// 數(shù)據(jù)庫操作 - 要求非事務(wù)環(huán)境
}2.7 ?? NESTED
?? 生活類比:就像俄羅斯套娃,在大娃娃中還有一個(gè)小娃娃。小娃娃可以獨(dú)立被取出或放回,但如果大娃娃被丟棄,小娃娃也會(huì)跟著一起消失。
Propagation.NESTED
- 含義:如果當(dāng)前存在事務(wù),則創(chuàng)建一個(gè)事務(wù)作為當(dāng)前事務(wù)的嵌套事務(wù)來運(yùn)行;如果當(dāng)前沒有事務(wù),則等效于REQUIRED。
- 應(yīng)用場景:適用于需要嵌套事務(wù)的場景,內(nèi)部事務(wù)的回滾不影響外部事務(wù)。
- 特點(diǎn):
- 使用保存點(diǎn)機(jī)制,可以回滾到保存點(diǎn)。
- 內(nèi)部事務(wù)回滾不影響外部事務(wù)。
- 外部事務(wù)回滾會(huì)導(dǎo)致內(nèi)部事務(wù)也回滾。
- 依賴于特定的事務(wù)管理器實(shí)現(xiàn)(如JDBC DataSourceTransactionManager)。
- 適合處理可以部分回滾的業(yè)務(wù)邏輯,如批量操作中允許部分失敗。
@Transactional
public void methodA() {
// 數(shù)據(jù)庫操作 - 事務(wù)A
try {
methodB(); // 執(zhí)行嵌套事務(wù)B
} catch (Exception e) {
// 捕獲異常,事務(wù)B回滾,事務(wù)A可以繼續(xù)
}
// 繼續(xù)事務(wù)A的操作
}
@Transactional(propagation = Propagation.NESTED)
public void methodB() {
// 數(shù)據(jù)庫操作 - 在嵌套事務(wù)B中
// 如果這里拋出異常,只有事務(wù)B回滾到保存點(diǎn),事務(wù)A可以繼續(xù)
}3. ?? 事務(wù)傳播行為對比表
?? 導(dǎo)航指南:面對這么多傳播行為,如何選擇最合適的一個(gè)?下面的對比表將幫助你快速了解各種傳播行為的特點(diǎn)和適用場景,就像一張地圖指引你在Spring事務(wù)的世界中找到正確的方向。
| 傳播行為 | 當(dāng)前有事務(wù) | 當(dāng)前無事務(wù) | 是否創(chuàng)建新事務(wù) | 異常回滾影響 | 適用場景 |
|---|---|---|---|---|---|
| ?? REQUIRED | 加入當(dāng)前事務(wù) | 創(chuàng)建新事務(wù) | 可能 | 全部回滾 | 默認(rèn)選擇,大多數(shù)業(yè)務(wù)場景 |
| ?? SUPPORTS | 加入當(dāng)前事務(wù) | 非事務(wù)執(zhí)行 | 否 | 跟隨外部事務(wù) | 查詢操作,可選事務(wù) |
| ?? MANDATORY | 加入當(dāng)前事務(wù) | 拋出異常 | 否 | 全部回滾 | 強(qiáng)制要求事務(wù)環(huán)境 |
| ?? REQUIRES_NEW | 掛起當(dāng)前事務(wù),創(chuàng)建新事務(wù) | 創(chuàng)建新事務(wù) | 是 | 獨(dú)立回滾 | 獨(dú)立操作,如日志記錄 |
| ?? NOT_SUPPORTED | 掛起當(dāng)前事務(wù) | 非事務(wù)執(zhí)行 | 否 | 不影響外部事務(wù) | 耗時(shí)的只讀操作 |
| ?? NEVER | 拋出異常 | 非事務(wù)執(zhí)行 | 否 | 不涉及事務(wù) | 必須非事務(wù)環(huán)境 |
| ?? NESTED | 創(chuàng)建嵌套事務(wù) | 等同REQUIRED | 嵌套 | 可部分回滾 | 批量處理,部分失敗場景 |
3.1 ?? 傳播行為決策流程圖
?? 思維導(dǎo)圖:下面的流程圖展示了Spring如何根據(jù)當(dāng)前事務(wù)環(huán)境和傳播行為做出決策。這就像一個(gè)交通指揮員,根據(jù)道路情況為每個(gè)車輛指定最合適的行駛路線。
?? 開始調(diào)用方法
↓
?? 檢查當(dāng)前是否存在事務(wù)?
↓ ↓
是 ? 否 ?
↓ ↓
?? 根據(jù)傳播行為決定: ?? 根據(jù)傳播行為決定:
- ?? REQUIRED: 加入 - ?? REQUIRED: 創(chuàng)建新事務(wù)
- ?? SUPPORTS: 加入 - ?? SUPPORTS: 非事務(wù)執(zhí)行
- ?? MANDATORY: 加入 - ?? MANDATORY: 拋異常
- ?? REQUIRES_NEW: 新建 - ?? REQUIRES_NEW: 創(chuàng)建新事務(wù)
- ?? NOT_SUPPORTED: 掛起- ?? NOT_SUPPORTED: 非事務(wù)執(zhí)行
- ?? NEVER: 拋異常 - ?? NEVER: 非事務(wù)執(zhí)行
- ?? NESTED: 嵌套事務(wù) - ?? NESTED: 創(chuàng)建新事務(wù)4. ?? 事務(wù)傳播行為的實(shí)際應(yīng)用場景
?? 實(shí)戰(zhàn)指南:理論知識已經(jīng)掌握,但如何在實(shí)際項(xiàng)目中應(yīng)用這些傳播行為?下面我們通過真實(shí)業(yè)務(wù)場景來展示各種傳播行為的最佳應(yīng)用方式,幫助你在實(shí)踐中做出正確的選擇。
4.1 ?? REQUIRED的應(yīng)用場景
?? 場景示例:想象一個(gè)電商平臺的訂單支付流程,需要同時(shí)更新訂單狀態(tài)、扣減庫存和生成支付記錄,這三個(gè)操作要么全部成功,要么全部失敗。
適用于大多數(shù)業(yè)務(wù)場景,特別是那些需要保證數(shù)據(jù)一致性的操作。例如:
- 訂單創(chuàng)建和庫存更新必須在同一個(gè)事務(wù)中。
- 用戶注冊時(shí),創(chuàng)建用戶信息和初始化用戶配置必須同時(shí)成功或失敗。
4.2 ?? REQUIRES_NEW的應(yīng)用場景
?? 場景示例:想象一個(gè)銀行轉(zhuǎn)賬系統(tǒng),無論轉(zhuǎn)賬是否成功,都需要記錄操作日志用于審計(jì)和監(jiān)管。即使轉(zhuǎn)賬失敗并回滾,審計(jì)日志也必須保留。
適用于需要獨(dú)立事務(wù)的場景,例如:
- 記錄操作日志:即使主業(yè)務(wù)失敗,也希望保留操作日志。
- 發(fā)送通知消息:即使主業(yè)務(wù)回滾,通知消息也應(yīng)該發(fā)送出去。
- 異步任務(wù)觸發(fā):主流程完成某步驟后,需要觸發(fā)一個(gè)獨(dú)立的異步任務(wù)。
@Transactional
public void createOrder(Order order) {
// 創(chuàng)建訂單
orderRepository.save(order);
// 記錄操作日志(使用獨(dú)立事務(wù))
logService.recordLog("創(chuàng)建訂單: " + order.getId());
// 如果這里發(fā)生異常,訂單創(chuàng)建會(huì)回滾,但日志記錄不會(huì)回滾
updateInventory(order.getItems());
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordLog(String message) {
logRepository.save(new Log(message));
}
}4.3 ?? NESTED的應(yīng)用場景
?? 場景示例:想象一個(gè)批量導(dǎo)入系統(tǒng),需要處理成千上萬條記錄。如果要求全部成功或全部失敗,那么一條記錄的錯(cuò)誤就會(huì)導(dǎo)致整個(gè)批處理失敗,效率極低。使用嵌套事務(wù),可以讓每條記錄單獨(dú)處理,出錯(cuò)的記錄回滾不影響其他記錄。
適用于可以部分回滾的場景,例如:
- 批量操作中,允許部分成功部分失敗。
- 多步驟操作,后續(xù)步驟失敗不影響前面步驟的結(jié)果。
- 復(fù)雜業(yè)務(wù)流程中的可選子流程,允許子流程失敗而不影響主流程。
@Transactional
public void processBatch(List<Item> items) {
for (Item item : items) {
try {
processItem(item);
} catch (Exception e) {
// 記錄錯(cuò)誤,繼續(xù)處理下一個(gè)
logError(item, e);
}
}
}
@Transactional(propagation = Propagation.NESTED)
private void processItem(Item item) {
// 處理單個(gè)項(xiàng)目
// 如果這里拋出異常,只會(huì)回滾這個(gè)項(xiàng)目的處理
}5. ?? 事務(wù)傳播行為與隔離級別的關(guān)系
?? 雙重保障:如果說事務(wù)傳播行為決定了"事務(wù)的邊界",那么隔離級別則決定了"事務(wù)的質(zhì)量"。就像一個(gè)安全系統(tǒng),傳播行為決定誰能進(jìn)入安全區(qū)域,而隔離級別決定進(jìn)入后的安全等級。
事務(wù)傳播行為定義了事務(wù)的邊界和傳播方式,而隔離級別定義了事務(wù)之間的隔離程度。兩者結(jié)合使用,可以更好地控制事務(wù)的行為。
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED
)
public void someMethod() {
// 方法體
}6. ?? 事務(wù)傳播行為的注意事項(xiàng)
?? 避坑指南:即使你已經(jīng)掌握了Spring事務(wù)的基本概念,在實(shí)際應(yīng)用中仍然可能遇到一些意想不到的問題。以下是一些常見的陷阱和注意事項(xiàng),幫助你在開發(fā)中避免這些潛在的問題。
6.1 ?? 自調(diào)用問題
?? 問題類比:就像你對著鏡子喊話,聲音不會(huì)傳得更遠(yuǎn)。同樣,在同一個(gè)類中直接調(diào)用自己的方法,Spring的事務(wù)魔法也無法生效。
Spring事務(wù)是通過AOP實(shí)現(xiàn)的,當(dāng)一個(gè)事務(wù)方法在同一個(gè)類中調(diào)用另一個(gè)事務(wù)方法時(shí),事務(wù)傳播行為可能不會(huì)生效。這是因?yàn)榉椒ㄕ{(diào)用沒有經(jīng)過代理對象。
@Service
public class UserService {
@Transactional
public void createUser(User user) {
// 保存用戶
saveUser(user);
// 問題:這里直接調(diào)用同類中的方法,updateUserStats的事務(wù)設(shè)置不會(huì)生效
updateUserStats();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateUserStats() {
// 更新統(tǒng)計(jì)信息
}
}解決方案:
- 使用自注入:
@Service
public class UserService {
@Autowired
private UserService self;
@Transactional
public void createUser(User user) {
// 保存用戶
saveUser(user);
// 通過自注入的代理對象調(diào)用,事務(wù)設(shè)置會(huì)生效
self.updateUserStats();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateUserStats() {
// 更新統(tǒng)計(jì)信息
}
}- 將方法移到另一個(gè)服務(wù)類中:
@Service
public class UserService {
@Autowired
private StatsService statsService;
@Transactional
public void createUser(User user) {
// 保存用戶
saveUser(user);
// 通過另一個(gè)服務(wù)調(diào)用,事務(wù)設(shè)置會(huì)生效
statsService.updateUserStats();
}
}
@Service
public class StatsService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateUserStats() {
// 更新統(tǒng)計(jì)信息
}
}6.2 ?? 事務(wù)超時(shí)設(shè)置
? 時(shí)間管理:就像給會(huì)議設(shè)置時(shí)間限制一樣,事務(wù)也需要有超時(shí)機(jī)制,避免長時(shí)間運(yùn)行的事務(wù)占用過多資源,影響系統(tǒng)整體性能。
在使用REQUIRES_NEW或NESTED等傳播行為時(shí),可以為不同的事務(wù)設(shè)置不同的超時(shí)時(shí)間。
@Transactional(timeout = 30) // 30秒超時(shí)
public void methodA() {
// 長時(shí)間操作
methodB();
}
@Transactional(propagation = Propagation.REQUIRES_NEW, timeout = 5) // 5秒超時(shí)
public void methodB() {
// 短時(shí)間操作
}6.3 ?? 只讀事務(wù)
?? 性能提升:就像圖書館的閱覽室只允許閱讀不允許修改書籍,只讀事務(wù)告訴數(shù)據(jù)庫"我只是來看看,不會(huì)做任何修改",從而讓數(shù)據(jù)庫可以優(yōu)化處理方式。
對于只讀操作,可以將事務(wù)設(shè)置為只讀,這樣可以優(yōu)化性能。
@Transactional(readOnly = true)
public List<User> getAllUsers() {
return userRepository.findAll();
}6.4 ?? 事務(wù)管理器的選擇
??? 工具選擇:就像不同的工作需要不同的工具,不同的數(shù)據(jù)訪問技術(shù)也需要匹配相應(yīng)的事務(wù)管理器。選擇合適的事務(wù)管理器就像選擇合適的工具,能讓你的工作事半功倍。
不同的事務(wù)傳播行為可能需要不同的事務(wù)管理器支持。例如,NESTED傳播行為需要使用支持保存點(diǎn)的事務(wù)管理器,如JDBC的DataSourceTransactionManager。
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}7. ? 性能優(yōu)化建議
?? 性能加速:事務(wù)雖然保證了數(shù)據(jù)的一致性,但不恰當(dāng)?shù)氖褂每赡軐?dǎo)致性能問題。就像賽車手需要在彎道減速直道加速一樣,我們也需要在不同場景下調(diào)整事務(wù)策略,以獲得最佳性能。
7.1 ?? 合理選擇傳播行為
// 優(yōu)化前:所有方法都使用REQUIRED
@Transactional(propagation = Propagation.REQUIRED)
public List<User> queryUsers() {
return userRepository.findAll(); // 只讀操作不需要事務(wù)
}
// 優(yōu)化后:只讀操作使用SUPPORTS或設(shè)置readOnly
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public List<User> queryUsers() {
return userRepository.findAll();
}7.2 ?? 避免長事務(wù)
? 性能瓶頸:長事務(wù)就像占用跑道太久的飛機(jī),會(huì)阻塞其他飛機(jī)的起降。在高并發(fā)系統(tǒng)中,長事務(wù)會(huì)導(dǎo)致數(shù)據(jù)庫連接被長時(shí)間占用,鎖定資源,降低系統(tǒng)吞吐量。
// 避免:長時(shí)間的事務(wù)
@Transactional
public void processLargeDataSet() {
List<Data> dataList = dataRepository.findAll(); // 可能很大的數(shù)據(jù)集
for (Data data : dataList) {
// 復(fù)雜的業(yè)務(wù)邏輯處理
complexBusinessLogic(data);
dataRepository.save(data);
}
}
// 推薦:分批處理
@Transactional
public void processLargeDataSetInBatches() {
int pageSize = 100;
int pageNumber = 0;
Page<Data> dataPage;
do {
dataPage = dataRepository.findAll(PageRequest.of(pageNumber, pageSize));
processBatch(dataPage.getContent());
pageNumber++;
} while (dataPage.hasNext());
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
private void processBatch(List<Data> batch) {
for (Data data : batch) {
complexBusinessLogic(data);
dataRepository.save(data);
}
}7.3 ?? 事務(wù)邊界優(yōu)化
?? 精準(zhǔn)控制:事務(wù)邊界就像圍欄,應(yīng)該只圍住真正需要事務(wù)保護(hù)的核心業(yè)務(wù)邏輯。將非核心操作(如發(fā)送郵件、記錄日志)移出事務(wù)邊界,可以顯著提高系統(tǒng)性能。
// 優(yōu)化前:事務(wù)邊界過大
@Transactional
public void createUserWithNotification(User user) {
userRepository.save(user);
// 發(fā)送郵件通知(耗時(shí)操作)
emailService.sendWelcomeEmail(user.getEmail());
// 記錄日志
logService.recordUserCreation(user.getId());
}
// 優(yōu)化后:縮小事務(wù)邊界
@Transactional
public void createUser(User user) {
userRepository.save(user);
}
public void createUserWithNotification(User user) {
createUser(user); // 核心業(yè)務(wù)在事務(wù)中
// 異步發(fā)送郵件
asyncEmailService.sendWelcomeEmail(user.getEmail());
// 獨(dú)立事務(wù)記錄日志
logService.recordUserCreation(user.getId());
}8. ?? 常見問題與故障排查
?? 排障指南:即使你按照最佳實(shí)踐編寫代碼,有時(shí)事務(wù)仍然可能不按預(yù)期工作。以下是一些常見問題及其解決方案,幫助你快速定位和修復(fù)事務(wù)相關(guān)的問題。
8.1 ?? 事務(wù)不生效的常見原因
?? 問題診斷:當(dāng)你發(fā)現(xiàn)事務(wù)沒有按預(yù)期回滾或提交時(shí),可以從以下幾個(gè)常見原因入手排查。
問題1:方法不是public
// 錯(cuò)誤:private方法上的@Transactional不會(huì)生效
@Transactional
private void saveUser(User user) {
userRepository.save(user);
}
// 正確:使用public方法
@Transactional
public void saveUser(User user) {
userRepository.save(user);
}問題2:自調(diào)用問題
// 問題代碼
@Service
public class UserService {
@Transactional
public void createUser(User user) {
saveUser(user);
updateUserStats(); // 這里的事務(wù)設(shè)置不會(huì)生效
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateUserStats() {
// 統(tǒng)計(jì)更新
}
}問題3:異常被捕獲
// 錯(cuò)誤:捕獲了異常,事務(wù)不會(huì)回滾
@Transactional
public void riskyOperation() {
try {
// 可能拋出異常的操作
dangerousMethod();
} catch (Exception e) {
// 異常被捕獲,事務(wù)不會(huì)回滾
log.error("操作失敗", e);
}
}
// 正確:重新拋出異常或手動(dòng)回滾
@Transactional
public void riskyOperation() {
try {
dangerousMethod();
} catch (Exception e) {
log.error("操作失敗", e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
throw e; // 重新拋出異常
}
}8.2 ? 性能問題排查
?? 性能診斷:事務(wù)執(zhí)行緩慢可能導(dǎo)致系統(tǒng)響應(yīng)遲鈍,用戶體驗(yàn)下降。就像醫(yī)生需要監(jiān)測病人的體溫和心率一樣,我們也需要監(jiān)控事務(wù)的執(zhí)行時(shí)間,及時(shí)發(fā)現(xiàn)性能瓶頸。
監(jiān)控事務(wù)執(zhí)行時(shí)間
@Component
@Aspect
public class TransactionMonitorAspect {
private static final Logger logger = LoggerFactory.getLogger(TransactionMonitorAspect.class);
@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
public Object monitorTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
String methodName = joinPoint.getSignature().getName();
try {
Object result = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - startTime;
if (executionTime > 1000) { // 超過1秒的事務(wù)記錄警告
logger.warn("長事務(wù)檢測: 方法 {} 執(zhí)行時(shí)間: {}ms", methodName, executionTime);
}
return result;
} catch (Exception e) {
long executionTime = System.currentTimeMillis() - startTime;
logger.error("事務(wù)執(zhí)行失敗: 方法 {} 執(zhí)行時(shí)間: {}ms", methodName, executionTime, e);
throw e;
}
}
}8.3 ?? 死鎖問題排查
?? 交通堵塞:死鎖就像兩輛車在狹窄的道路上相向而行,誰都不愿意讓步,最終導(dǎo)致雙方都無法前進(jìn)。在數(shù)據(jù)庫事務(wù)中,當(dāng)多個(gè)事務(wù)互相等待對方釋放鎖時(shí),就會(huì)發(fā)生死鎖。
// 容易產(chǎn)生死鎖的代碼
@Transactional
public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
Account fromAccount = accountRepository.findById(fromAccountId);
Account toAccount = accountRepository.findById(toAccountId);
fromAccount.withdraw(amount);
toAccount.deposit(amount);
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
}
// 避免死鎖的改進(jìn)版本
@Transactional
public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
// 按ID排序獲取鎖,避免死鎖
Long firstId = Math.min(fromAccountId, toAccountId);
Long secondId = Math.max(fromAccountId, toAccountId);
Account firstAccount = accountRepository.findByIdForUpdate(firstId);
Account secondAccount = accountRepository.findByIdForUpdate(secondId);
Account fromAccount = fromAccountId.equals(firstId) ? firstAccount : secondAccount;
Account toAccount = toAccountId.equals(firstId) ? firstAccount : secondAccount;
fromAccount.withdraw(amount);
toAccount.deposit(amount);
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
}9. ?? 最佳實(shí)踐
?? 實(shí)踐精華:經(jīng)過多年的項(xiàng)目實(shí)踐和經(jīng)驗(yàn)總結(jié),以下是使用Spring事務(wù)的最佳實(shí)踐,幫助你避開常見陷阱,寫出高質(zhì)量、可維護(hù)的事務(wù)代碼。
9.1 ?? 事務(wù)注解使用規(guī)范
?? 規(guī)范指引:就像良好的代碼風(fēng)格可以提高可讀性一樣,統(tǒng)一的事務(wù)注解使用規(guī)范可以讓團(tuán)隊(duì)成員更容易理解和維護(hù)事務(wù)代碼。
// 推薦:在Service層使用事務(wù)
@Service
@Transactional(readOnly = true) // 類級別設(shè)置默認(rèn)只讀
public class UserService {
@Transactional // 寫操作覆蓋類級別設(shè)置
public void createUser(User user) {
userRepository.save(user);
}
// 繼承類級別的只讀事務(wù)
public List<User> findAllUsers() {
return userRepository.findAll();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void auditLog(String operation, String details) {
auditRepository.save(new AuditLog(operation, details));
}
}9.2 ??? 異常處理最佳實(shí)踐
?? 安全防護(hù):良好的異常處理就像消防系統(tǒng),可以在問題發(fā)生時(shí)將損失降到最低。在事務(wù)中,正確處理異常不僅關(guān)系到數(shù)據(jù)一致性,還影響著系統(tǒng)的健壯性和用戶體驗(yàn)。
@Service
public class OrderService {
@Transactional(rollbackFor = Exception.class) // 所有異常都回滾
public void createOrder(Order order) throws OrderException {
try {
validateOrder(order);
orderRepository.save(order);
updateInventory(order.getItems());
} catch (ValidationException e) {
// 業(yè)務(wù)異常,記錄日志但不回滾
logService.recordValidationError(order.getId(), e.getMessage());
throw new OrderException("訂單驗(yàn)證失敗", e);
} catch (InventoryException e) {
// 庫存異常,需要回滾
throw new OrderException("庫存不足", e);
}
}
@Transactional(noRollbackFor = ValidationException.class)
public void processOrderWithPartialFailure(Order order) {
// 某些業(yè)務(wù)異常不需要回滾
}
}9.3 ?? 配置最佳實(shí)踐
?? 精細(xì)調(diào)優(yōu):就像調(diào)整汽車引擎以獲得最佳性能一樣,合理配置事務(wù)管理器可以讓你的應(yīng)用在不同場景下都能高效運(yùn)行。以下是一些常用的配置技巧。
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager manager = new DataSourceTransactionManager(dataSource);
// 設(shè)置默認(rèn)超時(shí)時(shí)間
manager.setDefaultTimeout(30);
// 設(shè)置事務(wù)同步
manager.setTransactionSynchronization(AbstractPlatformTransactionManager.SYNCHRONIZATION_ON_ACTUAL_TRANSACTION);
return manager;
}
// 自定義事務(wù)屬性
@Bean
public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
TransactionTemplate template = new TransactionTemplate(transactionManager);
template.setTimeout(30);
template.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
return template;
}
}9.4 ?? 編程式事務(wù)使用
?? 手動(dòng)控制:有時(shí)聲明式事務(wù)就像自動(dòng)駕駛,雖然方便但不夠靈活。編程式事務(wù)則像手動(dòng)擋汽車,讓你能夠在特定場景下精確控制事務(wù)的每個(gè)環(huán)節(jié),實(shí)現(xiàn)更復(fù)雜的業(yè)務(wù)邏輯。
@Service
public class ComplexBusinessService {
@Autowired
private TransactionTemplate transactionTemplate;
public void complexBusinessLogic() {
// 需要精確控制事務(wù)邊界的場景
String result = transactionTemplate.execute(status -> {
try {
// 事務(wù)內(nèi)的操作
performDatabaseOperations();
return "success";
} catch (Exception e) {
// 手動(dòng)回滾
status.setRollbackOnly();
return "failed";
}
});
// 事務(wù)外的操作
sendNotification(result);
}
}10. ?? 總結(jié)
?? 知識拼圖:恭喜你完成了Spring事務(wù)傳播機(jī)制的學(xué)習(xí)之旅!就像拼圖一樣,我們已經(jīng)將各個(gè)知識點(diǎn)組合成一幅完整的圖景。讓我們回顧一下這個(gè)旅程中的重要收獲。
Spring事務(wù)傳播機(jī)制是Spring事務(wù)管理的核心概念,通過合理使用不同的傳播行為,可以靈活控制事務(wù)的邊界和行為,滿足不同的業(yè)務(wù)需求。
?? 關(guān)鍵要點(diǎn)回顧:
?? 備忘錄:以下是你需要牢記的核心知識點(diǎn),它們將幫助你在實(shí)際項(xiàng)目中正確應(yīng)用事務(wù)傳播機(jī)制。
傳播行為選擇:
- REQUIRED:適用于大多數(shù)需要事務(wù)的場景
- REQUIRES_NEW:適用于需要獨(dú)立事務(wù)的場景
- NESTED:適用于需要嵌套事務(wù)的場景
- SUPPORTS:適用于可選事務(wù)的查詢操作
- MANDATORY:適用于必須在事務(wù)中執(zhí)行的方法
- NOT_SUPPORTED:適用于不需要事務(wù)的操作
- NEVER:適用于必須在非事務(wù)環(huán)境下執(zhí)行的操作
性能優(yōu)化:
- 合理設(shè)置事務(wù)邊界,避免長事務(wù)
- 只讀操作使用
readOnly=true - 分批處理大數(shù)據(jù)集
- 異步處理非核心業(yè)務(wù)
常見陷阱:
- 避免自調(diào)用問題
- 正確處理異常和回滾
- 注意方法訪問修飾符
- 防止死鎖問題
最佳實(shí)踐:
- 在Service層使用事務(wù)注解
- 合理配置事務(wù)管理器
- 監(jiān)控事務(wù)性能
- 編程式事務(wù)用于復(fù)雜場景
理解和正確使用事務(wù)傳播機(jī)制,對于保證應(yīng)用程序的數(shù)據(jù)一致性、可靠性和性能至關(guān)重要。在實(shí)際開發(fā)中,應(yīng)該根據(jù)具體的業(yè)務(wù)場景選擇合適的傳播行為,并結(jié)合性能監(jiān)控和故障排查,確保事務(wù)管理的有效性。
到此這篇關(guān)于Spring事務(wù)傳播機(jī)制詳解的文章就介紹到這了,更多相關(guān)Spring事務(wù)傳播機(jī)制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MyBatis?Mapper?接口是如何找到并執(zhí)行SQL的
本文將為你揭開MyBatis這層神秘的面紗,深入剖析其接口與SQL的映射原理,并總結(jié)在Spring?Boot環(huán)境下的最佳實(shí)踐和常見陷阱,感興趣的朋友跟隨小編一起看看吧2025-09-09
jvm中指定時(shí)區(qū)信息user.timezone問題及解決方式
同一份程序使用時(shí)間LocalDateTime類型,在國內(nèi)和國外部署后,返回的時(shí)間信息前端使用出問題,這篇文章主要介紹了jvm中指定時(shí)區(qū)信息user.timezone問題及解決方法,需要的朋友可以參考下2023-02-02
Druid基本配置及內(nèi)置監(jiān)控使用_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了Druid基本配置及內(nèi)置監(jiān)控使用,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-08-08
SpringBoot雪花算法主鍵ID傳到前端后精度丟失問題的解決
本文主要介紹了SpringBoot雪花算法主鍵ID傳到前端后精度丟失問題的解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
Spring注解@Value在controller無法獲取到值的解決
這篇文章主要介紹了Spring注解@Value在controller無法獲取到值的解決,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
Spring JPA聯(lián)表查詢之OneToOne源碼詳解
這篇文章主要為大家介紹了Spring JPA聯(lián)表查詢之OneToOne源碼詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04

