SpringBoot 中 @Transactional 的使用

SpringBoot 中 @Transactional 的使用

  • 一、@Transactional 的基本使用
  • 二、@Transactional 的核心屬性
  • 三、使用避坑(失效場景)
    • 3.1 自調用問題
    • 3.2 異常處理不當
    • 3.3 類未被 Spring 管理
    • 3.4 異步方法內使用失效
  • 四、工作實踐
    • 4.1 事務提交之后執行一些操作
    • 4.2 事務 + 分布式鎖的場景(先提交事務?先釋放鎖?)

在這里插入圖片描述

Spring Boot 開發中,@Transactional注解是實現數據庫事務管理的重要工具,它能確保數據操作的原子性、一致性、隔離性和持久性(ACID)。本文將深入探討@Transactional的使用,從基礎概念到實戰案例,再到常見的使用陷阱和工作實踐。

如果對@Transactional 的實現原理感興趣,可以參考這篇文章:【SpringBoot + MyBatis 事務管理全解析:從 @Transactional 到 JDBC Connection 的旅程】

一、@Transactional 的基本使用

@Transactional注解可以應用在類或方法上,用于聲明該類或方法需要進行事務管理。
@Transactional注解標注在類上時,該類中的所有公共方法都會被納入事務管理;當標注在方法上時,僅對該方法進行事務管理。

Spring Boot 項目中,首先確保在啟動類上添加了@EnableTransactionManagement注解,開啟事務管理功能。例如:

@SpringBootApplication
@EnableTransactionManagement
public class YourApplication {public static void main(String[] args) {SpringApplication.run(YourApplication.class, args);}
}

然后在需要事務管理的 Service 類或方法上添加@Transactional注解,如下:

@Service
public class UserService {private final UserRepository userRepository;public UserService(UserRepository userRepository) {this.userRepository = userRepository;}@Transactionalpublic void createUser(User user) {userRepository.save(user);// 假設這里還有其他數據庫操作// 如果任何操作失敗,整個事務將回滾}
}

上述代碼中,createUser方法被@Transactional注解修飾,當執行該方法時,如果userRepository.save(user)或后續的數據庫操作拋出異常,整個方法的操作都會回滾,保證數據的一致性。

二、@Transactional 的核心屬性

@Transactional注解有多個核心屬性,了解它們可以更靈活地控制事務行為,以下是主要屬性及其默認值:

  • propagation:事務傳播行為,定義了被調用方法的事務邊界。默認值為Propagation.REQUIRED。常見取值及含義如下
    • Propagation.REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新事務。這是最常用的傳播行為
    • Propagation.REQUIRES_NEW:創建一個新事務,如果當前存在事務,則將當前事務掛起。新事務獨立于當前事務,不受其影響
    • Propagation.SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務方式執行
    • Propagation.MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常
    • Propagation.NOT_SUPPORTED:以非事務方式執行,如果當前存在事務,則將當前事務掛起
    • Propagation.NEVER:以非事務方式執行,如果當前存在事務,則拋出異常
    • Propagation.NESTED:如果當前存在事務,則創建一個嵌套事務執行;如果當前沒有事務,則創建一個新事務。嵌套事務是一個子事務,它的提交和回滾不會影響外部事務,但外部事務回滾會導致嵌套事務回滾
  • isolation:事務隔離級別,用于解決事務并發訪問時可能出現的問題(如臟讀、不可重復讀、幻讀)。默認值為Isolation.DEFAULT,即使用數據庫默認的隔離級別(如 MySQL 默認的REPEATABLE_READ)。常見取值及含義如下

    • Isolation.DEFAULT:使用數據庫默認的隔離級別。
    • Isolation.READ_UNCOMMITTED:最低的隔離級別,允許讀取未提交的數據,可能會出現臟讀、不可重復讀和幻讀。
    • Isolation.READ_COMMITTED:只允許讀取已提交的數據,可以避免臟讀,但可能會出現不可重復讀和幻讀。
    • Isolation.REPEATABLE_READ:在一個事務內,多次讀取同一數據時結果一致,可以避免臟讀和不可重復讀,但可能會出現幻讀。
    • Isolation.SERIALIZABLE:最高的隔離級別,通過強制事務串行執行,避免了所有并發問題,但性能開銷最大。
  • timeout:事務的超時時間,單位為秒。如果事務執行時間超過該值,將自動回滾。默認值為 -1,表示事務沒有超時限制

  • rollbackFor:指定需要回滾的異常類型數組。只有當方法拋出的異常屬于指定的異常類型時,事務才會回滾。默認情況下,只有運行時異常(RuntimeException及其子類)和錯誤(Error及其子類)會導致事務回滾

  • noRollbackFor:指定不需要回滾的異常類型數組。當方法拋出的異常屬于指定的異常類型時,事務不會回滾

Propagation.REQUIREDPropagation.REQUIRES_NEW 的區別及案例

Propagation.REQUIREDPropagation.REQUIRES_NEW是最容易混淆的兩個傳播行為,通過以下示例來理解它們的區別。
假設我們有兩個 Service 方法:methodAmethodBmethodA調用methodB

使用 Propagation.REQUIRED

@Service
public class TransactionService {private final AnotherService anotherService;public TransactionService(AnotherService anotherService) {this.anotherService = anotherService;}@Transactional(propagation = Propagation.REQUIRED)public void methodA() {try {// 保存數據A// 假設這里執行成功// 調用methodBanotherService.methodB();// 模擬拋出異常throw new RuntimeException("methodA error");} catch (Exception e) {// 捕獲異常}}
}@Service
public class AnotherService {private final SomeRepository someRepository;public AnotherService(SomeRepository someRepository) {this.someRepository = someRepository;}@Transactional(propagation = Propagation.REQUIRED)public void methodB() {// 保存數據B// 假設這里執行成功}
}

在上述代碼中,methodAmethodB的傳播行為都為Propagation.REQUIRED。當methodA調用methodB時,methodB加入到methodA的事務中。由于methodA后續拋出了異常,整個事務回滾,數據 A 和數據 B 都不會被保存到數據庫

使用 Propagation.REQUIRES_NEW

@Service
public class TransactionService {private final AnotherService anotherService;public TransactionService(AnotherService anotherService) {this.anotherService = anotherService;}@Transactional(propagation = Propagation.REQUIRED)public void methodA() {try {// 保存數據A// 假設這里執行成功// 調用methodBanotherService.methodB();// 模擬拋出異常throw new RuntimeException("methodA error");} catch (Exception e) {// 捕獲異常}}
}@Service
public class AnotherService {private final SomeRepository someRepository;public AnotherService(SomeRepository someRepository) {this.someRepository = someRepository;}@Transactional(propagation = Propagation.REQUIRES_NEW)public void methodB() {// 保存數據B// 假設這里執行成功}
}

這里methodB的傳播行為為Propagation.REQUIRES_NEW,當methodA調用methodB時,methodB會創建一個新的獨立事務。即使methodA后續拋出異常導致自身事務回滾,methodB的事務已經提交,數據 B 會被成功保存到數據庫

三、使用避坑(失效場景)

在使用@Transactional注解時,存在一些常見的失效場景,需要特別注意:

3.1 自調用問題

自調用問題:在同一個類中,一個方法調用另一個被@Transactional注解的方法時,事務不會生效。這是因為 Spring 的事務管理是基于代理實現的,自調用時方法并沒有通過代理對象調用,所以事務不會起作用。

@Service
public class SelfCallService {@Transactionalpublic void outerMethod() {innerMethod();// 模擬拋出異常throw new RuntimeException("outerMethod error");}@Transactionalpublic void innerMethod() {// 數據庫操作}
}

上述代碼中,outerMethod調用innerMethod,由于是自調用,innerMethod的事務不會生效。當outerMethod拋出異常時,innerMethod中的數據庫操作不會回滾。解決方法是將被調用的方法抽取到另一個類中,通過依賴注入的方式調用。

3.2 異常處理不當

異常處理不當:如果在被@Transactional注解的方法中捕獲了異常,并且沒有重新拋出運行時異常或錯誤,事務不會回滾。因為 Spring 默認只有在拋出運行時異常或錯誤時才會觸發事務回滾。

@Service
public class ExceptionService {private final SomeRepository someRepository;public ExceptionService(SomeRepository someRepository) {this.someRepository = someRepository;}@Transactionalpublic void handleException() {try {// 數據庫操作// 假設這里拋出異常someRepository.save(new SomeEntity());} catch (Exception e) {// 捕獲異常但未重新拋出// 事務不會回滾}}
}

解決方法是在捕獲異常后,根據業務需求重新拋出運行時異常或合適的異常類型,以觸發事務回滾。

3.3 類未被 Spring 管理

類未被 Spring 管理:如果使用@Transactional注解的類沒有被 Spring 容器管理(例如沒有添加@Component@Service等注解),事務不會生效。因為 Spring 無法為其創建代理對象來管理事務。

3.4 異步方法內使用失效

異步方法內使用失效:在異步方法(使用@Async注解)內使用@Transactional注解,事務可能不會生效。這是因為異步方法是在另一個線程中執行,脫離了原有的事務上下文。如果需要在異步方法中使用事務,需要特殊處理,例如通過傳遞事務管理器等方式。

四、工作實踐

4.1 事務提交之后執行一些操作

事務提交之后執行一些操作:在某些場景下,需要在事務提交之后執行一些操作,例如發送消息通知、更新緩存等。可以使用TransactionSynchronizationManager來實現

假設我們有一個訂單處理系統,在訂單創建事務提交后,需要異步發送通知郵件。我們可以通過 TransactionSynchronization 接口監聽事務狀態,并在事務提交后執行郵件發送任務。

示例代碼:

@Service
public class OrderService {@Autowiredprivate OrderRepository orderRepository;@Autowiredprivate EmailService emailService;@Transactionalpublic void createOrder(Order order) {// 保存訂單到數據庫orderRepository.save(order);// 注冊事務同步器,監聽事務狀態TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {@Overridepublic void afterCommit() {// 事務提交后執行的操作emailService.sendOrderConfirmation(order.getId());}@Overridepublic void afterCompletion(int status) {// 事務完成后執行的操作(無論提交還是回滾)if (status == STATUS_COMMITTED) {// 事務已提交} else if (status == STATUS_ROLLED_BACK) {// 事務已回滾}}});// 其他業務邏輯...}
}@Service
public class EmailService {@Async // 異步方法public void sendOrderConfirmation(Long orderId) {// 模擬發送郵件System.out.println("異步發送訂單確認郵件,訂單ID: " + orderId);// 實際實現可能調用郵件服務API}
}

4.2 事務 + 分布式鎖的場景(先提交事務?先釋放鎖?)

在高并發業務場景下,如電商系統的訂單創建、金融系統的轉賬操作等,分布式鎖與事務的協同使用至關重要。若鎖釋放與事務提交順序不當,極易引發數據不一致問題。以下通過訂單創建場景,詳細說明正確的處理方式。

錯誤處理方式:先解鎖 再 提交事務

@Service
public class OrderServiceWrong {@Transactionalpublic void createOrderWrong(String orderNo, double amount) {String lockValue = lock(orderNo); // 加鎖try {if (orderMapper.existsByOrderNo(orderNo)) {return;}orderMapper.insert(new Order(orderNo, amount));} finally {// 錯誤:先釋放鎖,事務可能未提交unLock(orderNo, lockValue);}}
}

在上述代碼中,若線程 A 獲取鎖并完成訂單創建操作后,先執行了鎖釋放邏輯。此時,若事務提交因網絡延遲等原因未完成,線程 B 可能獲取到鎖并再次執行訂單創建邏輯,導致訂單重復創建,破壞數據一致性。

正確處理方式一:事務方法外部釋放鎖

@Service
public class OrderServiceCorrect2 {public void createOrderCorrect2(String orderNo, double amount) {String lockValue = lock(orderNo); // 加鎖try {// 調用帶事務的方法boolean success = createOrderInTransaction(orderNo, amount);} catch (Exception e) {// ...} finally {unLock(orderNo, lockValue); // 釋放鎖}}@Transactionalpublic boolean createOrderInTransaction(String orderNo, double amount) {if (orderMapper.existsByOrderNo(orderNo)) {return false;}orderMapper.insert(new Order(orderNo, amount));return true;}
}

正確處理方式二:使用 TransactionSynchronizationManager

@Service
public class OrderServiceCorrect1 {@Transactionalpublic void createOrderCorrect1(String orderNo, double amount) {String lockValue = lock(orderNo); // 加鎖try {if (orderMapper.existsByOrderNo(orderNo)) {return;}orderMapper.insert(new Order(orderNo, amount));// 在事務上下文中注冊同步器,確保鎖在事務提交后釋放TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {@Overridepublic void afterCompletion(int status) {if (status == STATUS_COMMITTED) {unLock(orderNo, lockValue); // 事務提交后釋放鎖}}});} catch (Exception e) {// 異常處理邏輯unLock(orderNo, lockValue); // 發生異常時直接釋放鎖throw e;}}
}

結束,??ヽ(°▽°)ノ? !!!

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

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

相關文章

6.26_JAVA_微服務_Elasticsearch

1、ES文檔中keyword意思是:字符串,但不需要分詞 2、ES細節CreateIndexRequest request new CreateIndexRequest("items");會讓你導包,會有兩個選擇: import org.elasticsearch.action.admin.indices.create.CreateInd…

Java 大視界 -- 基于 Java 的大數據可視化在智慧城市能源消耗動態監測與優化決策中的應用(324)

Java 大視界 -- 基于 Java 的大數據可視化在智慧城市能源消耗動態監測與優化決策中的應用(324) 引言:正文:一、Java 驅動的能源數據采集與預處理基建1.1 多源異構數據合規接入層(ISO 50001IEC 61850 雙標準適配&#x…

C++ 快速回顧(二)

C 快速回顧(二) 前言一、友元類二、友元函數三、深淺拷貝淺拷貝深拷貝 前言 用于快速回顧之前遺漏或者補充C知識 一、友元類 友元的優點是可以快速的輕松的訪問的原本由于私有保護的字段和函數,同時這也是它的缺點這樣破壞了原本封裝性。 …

ldl-DeserializationViewer一款強大的序列化數據可視化工具

ldl-DeserializationViewer 一款強大的序列化數據可視化工具,能夠將Java序列化的緩存數據轉換為可讀的JSON格式,無需原始DTO類定義。 A powerful visualization tool for serialized data that converts Java serialized cache data to readable JSON f…

NetworkSecurity SIG成立,助力國產操作系統安全生態發展

近期,ZeroOnes實驗室團隊成員在OpenAtom openKylin(簡稱“openKylin”)社區發起成立NetworkSecurity SIG,負責基于openKylin系統開展網絡安全工具的研發與適配,助力國產操作系統安全生態發展。 ZeroOnes實驗室專注于網…

回歸任務與分類任務的區別

回歸任務(Regression)與分類任務(Classification)是機器學習的兩大核心任務類型,其根本區別在于輸出變量的性質和任務目標。以下是系統性對比: 1. 本質區別:輸出變量類型 任務類型輸出&#xf…

Webshell工具的流量特征分析(菜刀,蟻劍,冰蝎,哥斯拉)

Webshell工具的流量特征分析(菜刀,蟻劍,冰蝎,哥斯拉) 0x00 前言 使用各種的shell工具獲取到目標權限,即可進行數據操作,今天來簡要分析一下目前常使用的各類shell管理工具的流量特診&#xff…

【linux】全志Tina配置swupdate工具進行分區打包

一、文件路徑 1、描述文件: .\build\swupdate\sw-description-ab 2、鏡像打包文件: .\build\swupdate\sw-subimgs-ab.cfg 二、文件作用 1、sw-description-ab 用于描述版本信息和ab區中要打包的分區信息以及掛載點。 2、sw-subimgs-ab.cfg 用于…

MicroPython網絡編程:AP模式與STA模式詳解

文章目錄 1. MicroPython網絡模塊概述2. 熱點AP模式詳解2.1 什么是AP模式?2.2 AP模式特點2.3 AP模式設置代碼2.4 AP模式適用場景3. 客戶端STA模式詳解3.1 什么是STA模式?3.2 STA模式特點3.3 STA模式設置代碼3.4 STA模式適用場景4. AP與STA模式對比分析5. 實際應用場景與選擇建…

Ubuntu網絡數據包發送工具大全

在Ubuntu系統中,有多種工具可以用于發送網絡數據包,包括UDP、TCP、ICMP等協議。以下是一些常用的工具及其簡要介紹: 1. Packet Sender 功能:支持發送和接收TCP、UDP和SSL數據包,提供圖形界面和命令行工具。安裝&…

小學期前端三件套學習(更新中)

第一階段 HTML 基礎結構 <!DOCTYPE html> <html><head><title>頁面標題</title></head><body>頁面內容</body> </html>常用內容標簽 文本類標簽 ? <h1>~<h6>&#xff1a;標題&#xff08;h1 每個頁面建…

高斯混合模型(Gaussian Mixture Model, GMM)

高斯混合模型&#xff08;Gaussian Mixture Model, GMM&#xff09; 是一種 概率模型&#xff0c;用于表示數據點由多個高斯分布&#xff08;Gaussian Distribution&#xff09;混合生成的過程。它廣泛應用于 聚類分析、密度估計、圖像分割、語音識別 等領域&#xff0c;尤其適…

MCP Client 開發 -32000 報錯

在開發 MCP Client 的過程中&#xff0c;發生了 -32000 報錯&#xff0c;源碼如下&#xff1a; import json from typing import Optional from contextlib import AsyncExitStackfrom openai import OpenAIfrom mcp import ClientSession, StdioServerParameters from mcp.cl…

使用zabbix監控Nginx服務的配置方法

準備 要監控Nginx的服務狀態&#xff0c;首先需要安裝nginx的status模塊&#xff1a;ngx_http_stub_status_module 首先 查看Nginx是否有安裝該模塊&#xff1a;--with-http_stub_status_module nginx -V 如果沒有安裝的話&#xff0c;安裝方法可以參照&#xff1a;Nginx新…

簡易服務器(TCP)

1.簡單介紹以及項目技術和開發環境 本文將通過epoll完成對客戶端請求的處理&#xff0c;通過多線程完成對客戶端發送數據的處理&#xff0c;并提交到遠端mysql 需要的使用到的一些技術有&#xff1a;socket網絡套接字編程、IO多路轉接的epoll、多線程&#xff08;包括互斥鎖和條…

總結前端三年 理想滾燙與現實的冰冷碰撞

大家好&#xff0c;我是500佰&#xff0c;技術宅男 目前正在前往獨立開發路線&#xff0c;我會在這里分享關于編程技術、獨立開發、技術資訊以及編程感悟等內容 6月3日的一篇《一個普通人的30歲 他經歷了什么》介紹一篇自己的碎碎念、即回顧自己以前的成長經歷&#xff0c;那么…

微服務網關/nacos/feign總結

現在學習到的組件 1.nacos&#xff1a;注冊中心&#xff0c;用于微服務之間交流的第三方管家&#xff0c;與生產者建立心跳契約對其監聽&#xff0c;注冊中心維護一張生產者的活躍表&#xff0c;會將活躍表實時更新并推送給消費者。 2.feign&#xff1a;nacos只是對生產者進行…

WebSocket 協議詳解

WebSocket 協議詳解 1. WebSocket 協議的幀數據詳解 1.1 幀結構 0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1--------------------------------------------------------|F|R|R|R| opco…

【辦公類-105-01】20250626 托小班報名表-條件格式-判斷雙胞胎EXCLE

背景需求&#xff1a; 今天招生主任電話問我&#xff0c;是否可以通過新生的EXCEL判斷幼兒是雙胞胎&#xff0c;便于分在一個班級里。 她試了EXCEL篩選升序&#xff0c;身份證號碼排序了&#xff0c;但是18個數字太多&#xff0c;看不出“雙胞胎” 于是把三個園區的名單發我來…

WPF Binding 的 Mode 屬性

前言 在 WPF 中&#xff0c;Binding 的 Mode 屬性決定了數據綁定的方向和行為。Mode 是 Binding 類的一個重要屬性&#xff0c;它指定了數據如何在源&#xff08;Source&#xff09;和目標&#xff08;Target&#xff09;之間流動。可用的 BindingMode 枚舉值有以下幾種&#…