“在同一事務中“ 的含義

一、"在同一事務中" 的核心含義

"在同一事務中" 指多個數據庫操作共享同一個事務上下文,具有以下特點:

  1. 原子性保證:所有操作要么全部成功提交,要么全部失敗回滾。
  2. 隔離性共享:操作使用相同的隔離級別(如 READ COMMITTED)。
  3. 資源共享:操作使用同一個數據庫連接,且事務狀態(如鎖)保持一致。

二、代碼中如何表示 "在同一事務中"

1. Spring 框架中的實現方式

在 Spring 中,主要通過 **@Transactional注解編程式事務 ** 來控制事務邊界。

示例 1:使用@Transactional注解(聲明式事務)

@Service
public class OrderService {@Autowiredprivate OrderRepository orderRepository;@Autowiredprivate PaymentService paymentService;// 該方法開啟一個事務,內部所有操作都在同一事務中@Transactional(propagation = Propagation.REQUIRED) // 默認值可不寫public void createOrder(Order order) {// 操作1:保存訂單orderRepository.save(order);// 操作2:扣減庫存(假設在同一事務中)inventoryService.reduceStock(order.getProductId(), order.getQuantity());// 操作3:調用支付服務(默認加入當前事務)paymentService.processPayment(order);// 若以上任一操作失敗,整個事務回滾}
}

關鍵點

  • @Transactional注解標記的方法會被 Spring AOP 攔截,自動開啟、提交或回滾事務。
  • 默認傳播行為Propagation.REQUIRED表示:若當前無事務,則創建新事務;若已有事務,則加入該事務。

示例 2:跨方法調用保持同一事務

因為@Transactional(propagation = Propagation.REQUIRED)是默認有的

@Service
public class OrderService {@Autowiredprivate PaymentService paymentService;// 外層事務方法@Transactionalpublic void processOrder(Order order) {// 操作1:創建訂單createOrder(order);// 操作2:調用支付服務(默認加入當前事務)paymentService.processPayment(order);// 若此處拋出異常,createOrder和processPayment都會回滾}// 內層方法(默認加入外層事務)public void createOrder(Order order) {// 訂單創建邏輯}
}

關鍵點

  • 同一個類中的方法調用(如processOrder調用createOrder)默認共享事務,因為 Spring AOP 通過代理對象實現事務增強。
  • createOrder單獨標記@Transactional,且調用者無事務,則createOrder會創建新事務。

2. 編程式事務(手動控制事務邊界)

適用于需要更細粒度控制事務的場景。

示例 3:使用 TransactionTemplate(Spring 早期方式)

@Service
public class TransactionExample {@Autowiredprivate TransactionTemplate transactionTemplate;@Autowiredprivate UserRepository userRepository;public void transferMoney(Long fromUserId, Long toUserId, BigDecimal amount) {transactionTemplate.execute(status -> {try {// 操作1:扣減轉出用戶余額User fromUser = userRepository.findById(fromUserId).orElseThrow();fromUser.setBalance(fromUser.getBalance().subtract(amount));userRepository.save(fromUser);// 模擬異常if (amount.compareTo(new BigDecimal("1000")) > 0) {throw new RuntimeException("金額過大");}// 操作2:增加轉入用戶余額User toUser = userRepository.findById(toUserId).orElseThrow();toUser.setBalance(toUser.getBalance().add(amount));userRepository.save(toUser);return true;} catch (Exception e) {// 手動回滾(實際中通常自動回滾)status.setRollbackOnly();throw e;}});}
}

關鍵點

  • transactionTemplate.execute()包裹的所有操作在同一事務中。
  • 異常會觸發事務回滾,成功則自動提交。

3. 使用 PlatformTransactionManager(更底層的方式)

關鍵點

  • 通過PlatformTransactionManager手動控制事務的開始、提交和回滾。
  • 適合需要動態調整事務屬性的場景。

三、常見問題與注意事項

1.事務傳播行為的影響

若子方法使用REQUIRES_NEW,則會創建新事務,與外層事務隔離。

示例:

@Transactional
public void parentMethod() {// 外層事務childService.childMethod(); // 若childMethod使用REQUIRES_NEW,則不在同一事務中
}

2.異常處理與事務回滾

Spring 默認只對RuntimeExceptionError回滾事務,檢查異常(如IOException)不會觸發回滾。

可通過@Transactional(rollbackFor = Exception.class)擴大回滾范圍。

3.同一個類中的方法調用

Spring AOP 通過代理對象實現事務增強,若methodA調用methodB(同一類中),methodB@Transactional會失效。

解決方案:

@Service
public class SelfCallExample {@Autowiredprivate SelfCallExample self; // 注入自身代理@Transactionalpublic void methodA() {// 正確方式:通過代理調用self.methodB();}@Transactionalpublic void methodB() {// ...}
}

四、總結

"在同一事務中" 的核心是共享事務上下文,在代碼中通過以下方式實現:

  1. 聲明式事務:使用@Transactional注解標記方法,默認傳播行為REQUIRED確保操作在同一事務中。
  2. 編程式事務:通過TransactionTemplatePlatformTransactionManager手動控制事務邊界。
  3. 跨方法調用:確保方法間通過代理對象調用,且子方法不使用REQUIRES_NEW等隔離傳播行為。

合理控制事務邊界是保證數據一致性的關鍵,需根據業務場景選擇合適的事務管理方式。





通俗易懂地理解 "同一事務" 與代碼示例



一、"同一事務" 的通俗解釋

比喻:想象你在銀行柜臺辦理轉賬業務,整個流程包括:

  1. 驗證轉出賬戶余額
  2. 扣減轉出賬戶金額
  3. 增加轉入賬戶金額
  4. 記錄交易日志

這四個步驟必須要么全部成功,要么全部失敗(例如,若扣錢成功但加錢失敗,銀行會回滾整個操作)。這就是 "在同一事務中" 的含義 ——一組不可分割的操作,共享同一個 "原子性" 保障

二、代碼示例:如何在 Spring 中實現 "同一事務"

1. 最常見場景:一個方法內的多個操作

@Service
public class UserService {@Autowiredprivate UserRepository userRepository;@Autowiredprivate LogRepository logRepository;// 整個方法在同一事務中@Transactionalpublic void transferMoney(Long fromId, Long toId, BigDecimal amount) {// 操作1:扣錢User fromUser = userRepository.findById(fromId).orElseThrow();fromUser.setBalance(fromUser.getBalance().subtract(amount));userRepository.save(fromUser);// 模擬網絡延遲或其他異常// if (true) throw new RuntimeException("模擬異常");// 操作2:加錢User toUser = userRepository.findById(toId).orElseThrow();toUser.setBalance(toUser.getBalance().add(amount));userRepository.save(toUser);// 操作3:記錄日志(與轉賬共享同一事務)Log log = new Log("轉賬", amount, fromId, toId);logRepository.save(log);}
}

關鍵點

  • @Transactional標記整個方法,內部的 3 個數據庫操作共享同一事務。
  • 若中間拋出異常(如取消注釋第 16 行),則所有操作都回滾,錢不會平白消失。

2. 跨方法調用保持同一事務

@Service
public class OrderService {@Autowiredprivate ProductService productService;@Autowiredprivate InventoryService inventoryService;// 主事務方法@Transactionalpublic void createOrder(Order order) {// 操作1:保存訂單orderRepository.save(order);// 操作2:扣減庫存(調用其他服務的方法)inventoryService.reduceStock(order.getProductId(), order.getQuantity());// 操作3:更新商品銷量(調用其他服務的方法)productService.updateSales(order.getProductId(), order.getQuantity());// 若此處拋出異常,整個事務回滾// throw new RuntimeException("訂單創建失敗");}
}@Service
public class InventoryService {// 該方法默認加入調用者的事務public void reduceStock(Long productId, Integer quantity) {Inventory inventory = inventoryRepository.findByProductId(productId);inventory.setStock(inventory.getStock() - quantity);inventoryRepository.save(inventory);}
}

關鍵點

  • createOrder方法上的@Transactional使整個調用鏈在同一事務中。
  • reduceStockupdateSales雖然在不同類中,但默認加入外層事務,共享原子性。
  • 若訂單保存成功,但扣庫存失敗,則整個操作回滾,不會出現 "有訂單但沒扣庫存" 的情況。

3. 同一類中方法調用的陷阱與解決方案

@Service
public class UserService {@Autowiredprivate UserRepository userRepository;@Autowiredprivate UserService self; // 注入自身代理// 錯誤示例:同一類中方法調用,事務不生效@Transactionalpublic void wrongUpdate(User user) {// 保存用戶基本信息userRepository.save(user);// 調用同一類中的方法(事務不會生效)updateLastLoginTime(user.getId());// 若此處拋出異常,updateLastLoginTime的操作不會回滾}@Transactionalpublic void updateLastLoginTime(Long userId) {User user = userRepository.findById(userId).orElseThrow();user.setLastLoginTime(new Date());userRepository.save(user);}// 正確示例:通過代理調用,事務生效@Transactionalpublic void correctUpdate(User user) {userRepository.save(user);// 通過代理調用,事務生效self.updateLastLoginTime(user.getId());}
}

關鍵點

  • Spring 通過代理對象實現事務增強,同一類中直接調用方法(如wrongUpdate)會導致內層方法的@Transactional失效。
  • 解決方案:通過@Autowired注入自身代理(self),或拆分到不同 Service 類中。

三、常見問題與避坑指南

1. 為什么要在同一事務中?

反例:若轉賬操作不在同一事務中:

  • 扣錢成功 → 系統崩潰 → 加錢失敗
  • 結果:錢平白消失,用戶炸鍋!

正例:在同一事務中,要么都成功,要么都失敗,錢不會消失。

2. 如何驗證是否在同一事務中?

@Service
public class TestService {@Autowiredprivate DataSource dataSource;@Transactionalpublic void testTransaction() {try (Connection conn = dataSource.getConnection()) {// 輸出false表示自動提交已關閉,在事務中System.out.println("是否自動提交: " + conn.getAutoCommit());} catch (SQLException e) {e.printStackTrace();}// 模擬異常throw new RuntimeException("回滾測試");}
}

關鍵點

  • @Transactional方法中,數據庫連接的autoCommitfalse,表示事務已開啟。
  • 若拋出異常,數據庫會自動回滾。

3. 哪些情況會導致事務失效?

  • 非 public 方法@Transactional只對 public 方法生效。
  • 同一類中直接調用:如前面示例中的wrongUpdate
  • 異常被吞掉
@Transactional
public void wrongMethod() {try {// 數據庫操作throw new RuntimeException("異常");} catch (Exception e) {// 錯誤:異常被捕獲,事務不會回滾}
}

四、總結

"同一事務" 的本質

  • 一組操作共享原子性(要么都成功,要么都失敗)。
  • 共享數據庫連接事務狀態

實現方式

  1. @Transactional標記方法,確保多個操作在同一方法內或跨方法調用。
  2. 注意同一類中方法調用的代理問題。
  3. 避免手動捕獲異常導致事務不回滾。

記住:只要看到@Transactional,就像給代碼上了 "保險",要么全成功,要么全失敗!




通俗易懂理解:新方法的事務選擇(加入現有事務還是新建事務)



一、用生活場景比喻事務傳播行為

場景假設:你和朋友一起做飯(現有事務),這時來了一個新幫手(新方法)。

  • 情況 1:新幫手加入你們的小組(加入現有事務)
    你們共用一口鍋、一套工具,任何一個人犯錯(比如菜炒糊了),整個做飯過程都可能取消(回滾)。
    對應代碼:新方法和現有方法在同一個事務中,共享成功或失敗。

  • 情況 2:新幫手自己開小灶(新建事務)
    他自己用另一口鍋做飯,即使你們的菜炒糊了,他的飯依然能單獨做好端出來;反之,他把飯做糊了,不影響你們的菜。
    對應代碼:新方法開啟獨立事務,與外層事務互不影響。

二、代碼示例:兩種事務傳播行為的對比

1. 新方法加入現有事務(默認行為:PROPAGATION_REQUIRED)

@Service
public class OrderService {@Autowiredprivate PaymentService paymentService;// 外層事務(主業務:創建訂單+支付)@Transactionalpublic void createOrderWithPayment(Order order) {// 操作1:保存訂單(現有事務)orderRepository.save(order);// 操作2:調用支付方法(默認加入現有事務)paymentService.pay(order.getOrderId(), order.getAmount());// 若此處拋出異常,整個事務回滾(訂單和支付都失敗)// throw new RuntimeException("訂單創建失敗");}
}@Service
public class PaymentService {// 未指定傳播行為,默認PROPAGATION_REQUIRED(加入現有事務)@Transactionalpublic void pay(Long orderId, BigDecimal amount) {// 支付操作Payment payment = new Payment(orderId, amount);paymentRepository.save(payment);// 若此處拋出異常,外層事務一起回滾// throw new RuntimeException("支付失敗");}
}

關鍵點

  • 外層createOrderWithPayment開啟事務,內層pay方法默認加入這個事務。
  • 異常連鎖反應:內層拋異常 → 外層事務回滾;外層拋異常 → 內層操作也回滾。

2. 新方法創建新事務(PROPAGATION_REQUIRES_NEW)

@Service
public class OrderService {@Autowiredprivate PaymentService paymentService;// 外層事務(主業務:創建訂單)@Transactionalpublic void createOrder(Order order) {// 操作1:保存訂單orderRepository.save(order);try {// 操作2:調用支付方法(新建獨立事務)paymentService.payWithNewTransaction(order.getOrderId(), order.getAmount());} catch (Exception e) {// 支付失敗不影響訂單保存log.error("支付失敗,但訂單已創建", e);}// 外層拋出異常,不影響內層已提交的支付// throw new RuntimeException("訂單創建失敗");}
}@Service
public class PaymentService {// 明確指定新建事務@Transactional(propagation = Propagation.REQUIRES_NEW)public void payWithNewTransaction(Long orderId, BigDecimal amount) {// 支付操作Payment payment = new Payment(orderId, amount);paymentRepository.save(payment);// 內層拋異常,僅回滾支付操作,不影響外層訂單throw new RuntimeException("支付失敗(獨立回滾)");}
}

關鍵點

  • payWithNewTransactionREQUIRES_NEW開啟新事務,與外層事務隔離。
  • 異常隔離
    • 內層拋異常 → 僅回滾支付操作,訂單保存成功;
    • 外層拋異常 → 訂單回滾,但已提交的支付操作不回滾(因為內層事務已獨立提交)。

三、常見應用場景對比

場景選擇加入現有事務(REQUIRED)選擇新建事務(REQUIRES_NEW)
典型案例轉賬(扣錢 + 加錢必須同時成功 / 失敗)訂單創建時記錄日志(即使訂單失敗,日志也要保存)
核心需求操作必須整體成功或失敗操作需要獨立于外層邏輯
資源消耗更省資源(共用數據庫連接)消耗更多資源(新建連接 + 事務)
異常處理內層異常會導致外層回滾內層異常不影響外層,外層異常不影響內層

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/909252.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/909252.shtml
英文地址,請注明出處:http://en.pswp.cn/news/909252.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Create my OS】從零編寫一個操作系統

前言: 相信每個自學操作系統的同學,大致學習路線都離不開 HIT-OS、MIT-6.S081、MIT-6.824、MIT-6.828等經典的公開課。但學習完這些經典公開課并完成相應的Lab,很多同學腦海中對于操作系統的知識其實都是零散的,讓你從頭開始編寫一…

計算機視覺與深度學習 | 低照度圖像增強算法綜述(開源鏈接,原理,公式,代碼)

低照度圖像增強算法綜述 1 算法分類與原理1.1 傳統方法1.2 深度學習方法2 核心算法詳解2.1 多尺度Retinex (MSRCR) 實現2.2 SCI自校準光照學習2.3 自適應伽馬校正2.4 WaveletMamba架構3 開源資源與實現3.1 主流算法開源庫3.2 關鍵代碼實現4 評估與實驗對比4.1 客觀評價指標4.2 …

【工具教程】批量PDF識別提取區域的內容重命名,將PDF指定區域位置的內容提取出來改名的具體操作步驟

在企業運營過程中,時常會面臨處理海量 PDF 文件的挑戰。從 PDF 指定區域提取內容并用于重命名文件,能極大地優化企業內部的文件管理流程,提升工作效率。以下為您詳細介紹其在企業中的應用場景、具體使用步驟及注意事項。? 詳細使用步驟? 選…

【Java多線程從青銅到王者】定時器的原理和實現(十一)

定時器 定時器時我們日常開發中會用到的組件工具,類似于一個"鬧鐘",設定一個時間,等到了時間,定時器最自動的去執行某個邏輯,比如博客的定時發布,就是使用到了定時器 Java標準庫里面也提供了定時…

深入剖析AI大模型:Prompt 優化的底層邏輯

記得看到一篇NLP的文章,就 Prompt 時序效應的論文揭示了一個有趣現象,文章中說:模型對指令的解析存在 "注意力衰減" 特性 —— 就像人類閱讀時會更關注段落開頭,模型對 Prompt 前 20% 的內容賦予的權重高達 60%。這個發…

【備忘】PHP web項目一般部署辦法

【PHP項目一般部署辦法】 操作步驟 代碼: 把php項目代碼clone到指定位置如www/下新建php站點,填寫域名,把站點根目錄設置為項目根目錄項目入口設置,一般為public/項目權限改為766(特殊時候可設置為777),如果有特殊要求…

【60 Pandas+Pyecharts | 箱包訂單數據分析可視化】

文章目錄 🏳??🌈 1. 導入模塊🏳??🌈 2. Pandas數據處理2.1 讀取數據2.2 數據信息2.3 去除訂單金額為空的數據2.5 提取季度和星期 🏳??🌈 3. Pyecharts數據可視化3.1 每月訂單量和訂單金額分布3.2 各季…

玩轉Docker | 使用Docker部署vaultwarden密碼管理器

玩轉Docker | 使用Docker部署vaultwarden密碼管理器 前言一、vaultwarden介紹Vaultwarden 簡介主要特點二、系統要求環境要求環境檢查Docker版本檢查檢查操作系統版本三、部署vaultwarden服務下載vaultwarden鏡像編輯部署文件創建容器檢查容器狀態檢查服務端口安全設置四、配置…

晶振的多面舞臺:從日常電子到高精尖科技的應用探秘

在現代科技的宏大舞臺上,晶振宛如一位低調卻至關重要的幕后主角,以其穩定的頻率輸出,為各類電子設備賦予了精準的“脈搏”。從我們日常生活中須臾不離的電子設備,到引領時代前沿的高精尖科技領域,晶振都發揮著不可替代…

uni-app 小程序 Cannot read property ‘addEventListener‘ of undefined, mounted hook

在用 uni-app 開發微信小程序時,提示 Cannot read property addEventListener of undefined, mounted hook document.addEventListener("mousemove", this.touchmove) 在小程序開發里,addEventListener 并非通用的標準 API,不過與…

《專業小詞開課啦》——冪等

在系統對接過程中,當出現接口調用異常的情況時,程序員可能會用一些專業術語來答疑......對于0基礎同學,自然是需要自行百度一番,學習一下! 接下來,先學習【冪等】 PS: 小白參考1.1~1.4內容即…

滲透實戰PortSwigger Labs指南:自定義標簽XSS和SVG XSS利用

阻止除自定義標簽之外的所有標簽 先輸入一些標簽測試&#xff0c;說是全部標簽都被禁了 除了自定義的 自定義<my-tag onmouseoveralert(xss)> <my-tag idx onfocusalert(document.cookie) tabindex1> onfocus 當元素獲得焦點時&#xff08;如通過點擊或鍵盤導航&…

利用pycharm搭建模型步驟

1 如何將別人論文的代碼跑起來&#xff0c;以Pycharm為例&#xff0c;在下載代碼的時候&#xff0c;要注意使用的python版本是多少&#xff0c;并且要注意使用的keras和tensorflow等文件夾的版本&#xff0c;我們可以直接使用pycharm中file文件中的settings&#xff0c;來添加相…

Qt 中directoryChanged監聽某個目錄的內容是否發生變化

Qt 中&#xff0c;directoryChanged 是 QFileSystemWatcher 類的一個信號&#xff0c;用于監聽某個目錄的內容是否發生變化&#xff08;如添加、刪除文件或子目錄&#xff09; ? 一、功能說明 QFileSystemWatcher::directoryChanged(const QString &path) 信號的作用是&…

JavaWeb(Servlet預習)

案例1&#xff1a;基于jspServlet實現用戶登錄驗證 1.input.jsp <% page language"java" contentType"text/html; charsetUTF-8"pageEncoding"UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset"UTF-8&q…

Docker Compose 部署 Prometheus + Grafana

安裝 docker-compose.yml version: 3.8services:# Prometheus 監控服務prometheus:image: prom/prometheus:latestcontainer_name: prometheusrestart: unless-stoppedvolumes:- ./conf/prometheus.yml:/etc/prometheus/prometheus.yml- ./prometheus_data:/prometheuscomman…

準確--使用 ThinBackup 插件執行備份和恢復

使用 ThinBackup 插件執行備份和恢復 導出&#xff08;備份&#xff09;步驟&#xff1a; 進入 Manage Jenkins > ThinBackup。設置 Backup schedule for full backups&#xff08;可選&#xff09;&#xff0c;并配置 Files to exclude&#xff08;可選&#xff09;。點擊…

Qt Creator 從入門到項目實戰

Qt Creator 簡介 Qt Creator 是一個跨平臺的集成開發環境&#xff08;IDE&#xff09;&#xff0c;專門用于開發 Qt 應用程序。它為開發者提供了一個強大的工具集&#xff0c;包括代碼編輯器、調試器、UI 設計器以及性能分析工具等。 1.1 Qt Creator 的安裝 Qt Creator 支持…

公司內網遠程訪問配置教程:本地服務器(和指定端口應用)實現外網連接使用

在數字化時代&#xff0c;企業的辦公模式日益多元化&#xff0c;遠程辦公、跨地區協作等需求不斷增加。這使得在公司內網中配置遠程訪問變得至關重要&#xff0c;它能讓員工無論身處何地&#xff0c;只要有網絡連接&#xff0c;就能便捷地訪問公司內部的各類資源&#xff0c;如…

邊緣計算如何重塑能源管理?從技術原理到應用場景全解析

在全球能源數字化轉型的浪潮中&#xff0c;一個看似不起眼的設備正在悄悄改變工業能效管理的模式 —— 這就是邊緣計算網關。以能源領域為例&#xff0c;傳統的 "設備 - 云端" 二層架構正面臨數據傳輸延遲、網絡帶寬壓力大、斷網失效等挑戰&#xff0c;而邊緣計算技術…