SpringBoot 事務(wù)深度解析之從理論到實踐的完整指南

前言
在后端開發(fā)中,數(shù)據(jù)一致性是衡量系統(tǒng)可靠性的核心指標之一,而事務(wù)正是保障數(shù)據(jù)一致性的關(guān)鍵技術(shù)。無論是電商系統(tǒng)的訂單支付、金融平臺的資金流轉(zhuǎn),還是企業(yè)級應(yīng)用的業(yè)務(wù)數(shù)據(jù)處理,都離不開事務(wù)的保駕護航。SpringBoot作為當(dāng)前最流行的Java開發(fā)框架,其對事務(wù)的支持簡潔高效,極大降低了開發(fā)者的使用成本。本文將從事務(wù)的基礎(chǔ)理論出發(fā),逐步深入SpringBoot事務(wù)的實現(xiàn)機制、使用方式、常見問題及優(yōu)化方案,幫助開發(fā)者全面掌握SpringBoot事務(wù)知識,輕松應(yīng)對實際開發(fā)中的數(shù)據(jù)一致性挑戰(zhàn)。
第一章 事務(wù)基礎(chǔ):你必須掌握的核心概念
1.1 什么是事務(wù)?
事務(wù)(Transaction)是數(shù)據(jù)庫操作的基本單元,它是由一組不可分割的數(shù)據(jù)庫操作組成的邏輯集合。這組操作要么全部執(zhí)行成功,要么全部執(zhí)行失敗,不存在部分執(zhí)行的中間狀態(tài)。例如,在電商下單場景中,需要完成“扣減庫存”“創(chuàng)建訂單”“扣減余額”三個操作,這三個操作就構(gòu)成一個事務(wù)。如果其中任意一個操作失敗,為了保證數(shù)據(jù)一致性,其他已執(zhí)行的操作必須回滾,恢復(fù)到操作前的狀態(tài)。
事務(wù)本質(zhì)上是數(shù)據(jù)庫提供的一種機制,主流的關(guān)系型數(shù)據(jù)庫如MySQL、Oracle、PostgreSQL等都原生支持事務(wù)。而SpringBoot則是在數(shù)據(jù)庫事務(wù)的基礎(chǔ)上,通過Spring框架的封裝,為開發(fā)者提供了更便捷、更靈活的事務(wù)管理能力。
1.2 事務(wù)的ACID特性
ACID是事務(wù)的四大核心特性,是衡量事務(wù)機制是否可靠的標準,也是理解事務(wù)本質(zhì)的關(guān)鍵。這四個特性相互關(guān)聯(lián),共同保障數(shù)據(jù)的一致性。
1.2.1 原子性(Atomicity)

原子性是指事務(wù)中的所有操作是一個不可分割的整體,如同“原子”一樣不可再分。事務(wù)的原子性要求:要么事務(wù)中的所有操作都執(zhí)行成功并提交,要么所有操作都執(zhí)行失敗并回滾,不存在“部分成功、部分失敗”的情況。
例如,銀行轉(zhuǎn)賬業(yè)務(wù)中,“從A賬戶扣減1000元”和“向B賬戶增加1000元”是一個事務(wù)的兩個操作。如果扣減操作成功但增加操作失敗,原子性會保證扣減的1000元回滾到A賬戶,避免出現(xiàn)A賬戶資金減少但B賬戶資金未增加的錯誤數(shù)據(jù)。
1.2.2 一致性(Consistency)

一致性是指事務(wù)執(zhí)行前后,數(shù)據(jù)庫中的數(shù)據(jù)必須處于一個合法、一致的狀態(tài),即數(shù)據(jù)需要滿足業(yè)務(wù)規(guī)則和完整性約束。一致性是事務(wù)的最終目標,其他三個特性都是為了保障一致性而存在的。
以電商庫存管理為例,假設(shè)某商品的初始庫存為100件。如果同時有兩個訂單分別購買10件和20件商品,那么兩個事務(wù)執(zhí)行完成后,庫存應(yīng)該變?yōu)?0件,這就是數(shù)據(jù)的一致性。如果由于事務(wù)機制缺陷,導(dǎo)致最終庫存為80件或90件,就破壞了數(shù)據(jù)的一致性。
1.2.3 隔離性(Isolation)

隔離性是指多個事務(wù)同時并發(fā)執(zhí)行時,一個事務(wù)的執(zhí)行過程不會被其他事務(wù)干擾,各個事務(wù)之間相互隔離,如同在獨立的環(huán)境中執(zhí)行一樣。如果事務(wù)之間沒有隔離性,多個并發(fā)事務(wù)操作同一批數(shù)據(jù)時,可能會出現(xiàn)臟讀、不可重復(fù)讀、幻讀等問題,從而破壞數(shù)據(jù)一致性。
例如,事務(wù)A正在修改用戶的余額,此時事務(wù)B讀取該用戶的余額。如果沒有隔離性,事務(wù)B可能會讀取到事務(wù)A修改過程中的中間數(shù)據(jù),而這個數(shù)據(jù)最終可能因為事務(wù)A回滾而失效,導(dǎo)致事務(wù)B基于錯誤的數(shù)據(jù)進行業(yè)務(wù)處理。
1.2.4 持久性(Durability)

持久性是指事務(wù)一旦執(zhí)行成功并提交,其對數(shù)據(jù)庫中數(shù)據(jù)的修改就是永久性的,即使后續(xù)發(fā)生數(shù)據(jù)庫崩潰、服務(wù)器宕機等故障,已提交的事務(wù)數(shù)據(jù)也不會丟失。數(shù)據(jù)庫通常通過將事務(wù)日志寫入磁盤來實現(xiàn)持久性,當(dāng)系統(tǒng)故障恢復(fù)時,可以通過事務(wù)日志恢復(fù)已提交的事務(wù)數(shù)據(jù)。
例如,用戶完成訂單支付后,事務(wù)提交成功,此時即使數(shù)據(jù)庫服務(wù)器突然斷電,再次啟動后,用戶的支付記錄和訂單狀態(tài)也不會丟失,確保業(yè)務(wù)數(shù)據(jù)的可靠。
1.3 事務(wù)的并發(fā)問題
在多用戶并發(fā)訪問數(shù)據(jù)庫的場景下,如果事務(wù)的隔離性得不到保障,就會出現(xiàn)一系列并發(fā)問題。根據(jù)問題的嚴重程度,主要分為以下四類:
1.3.1 臟讀(Dirty Read)
臟讀是指一個事務(wù)讀取到了另一個事務(wù)尚未提交的修改數(shù)據(jù)。由于未提交的事務(wù)可能會回滾,因此讀取到的數(shù)據(jù)是“臟”的,即無效數(shù)據(jù)。
場景示例:事務(wù)A執(zhí)行“扣減用戶余額100元”操作,但未提交;此時事務(wù)B讀取該用戶的余額,得到扣減后的金額;隨后事務(wù)A因為異?;貪L,用戶余額恢復(fù)為原始值,但事務(wù)B已經(jīng)基于讀取到的臟數(shù)據(jù)進行了后續(xù)業(yè)務(wù)處理(如創(chuàng)建訂單),導(dǎo)致業(yè)務(wù)錯誤。
1.3.2 不可重復(fù)讀(Non-repeatable Read)
不可重復(fù)讀是指在同一個事務(wù)中,多次讀取同一批數(shù)據(jù)時,得到的結(jié)果不一致。這種問題通常是由于在兩次讀取之間,有其他事務(wù)修改并提交了該數(shù)據(jù)。
場景示例:事務(wù)A第一次讀取用戶余額為1000元;此時事務(wù)B修改該用戶余額為800元并提交;事務(wù)A再次讀取該用戶余額時,得到的結(jié)果變?yōu)?00元,與第一次讀取的結(jié)果不一致,導(dǎo)致事務(wù)A的業(yè)務(wù)邏輯出現(xiàn)混亂。
1.3.3 幻讀(Phantom Read)
幻讀是指在同一個事務(wù)中,多次執(zhí)行相同的查詢語句時,查詢結(jié)果的行數(shù)不一致。這種問題通常是由于在兩次查詢之間,有其他事務(wù)插入或刪除了符合查詢條件的數(shù)據(jù)。
場景示例:事務(wù)A執(zhí)行“查詢余額大于500元的用戶”操作,得到10條記錄;此時事務(wù)B插入了一條余額為600元的用戶數(shù)據(jù)并提交;事務(wù)A再次執(zhí)行相同的查詢語句,得到11條記錄,如同出現(xiàn)了“幻覺”一樣。
1.3.4 丟失修改(Lost Update)
丟失修改是指兩個事務(wù)同時修改同一數(shù)據(jù),后提交的事務(wù)會覆蓋先提交的事務(wù)的修改結(jié)果,導(dǎo)致先提交的事務(wù)的修改丟失。
場景示例:事務(wù)A讀取用戶余額為1000元,計劃修改為900元;同時事務(wù)B也讀取該用戶余額為1000元,計劃修改為800元;事務(wù)A先提交,將余額改為900元;隨后事務(wù)B提交,將余額改為800元,導(dǎo)致事務(wù)A的修改被丟失。
1.4 數(shù)據(jù)庫事務(wù)隔離級別
為了解決事務(wù)的并發(fā)問題,數(shù)據(jù)庫定義了不同的事務(wù)隔離級別。隔離級別越高,事務(wù)的并發(fā)問題越少,但數(shù)據(jù)庫的并發(fā)性能也會越低。開發(fā)者需要根據(jù)業(yè)務(wù)場景在數(shù)據(jù)一致性和并發(fā)性能之間進行權(quán)衡。SQL標準定義了四個隔離級別,不同的數(shù)據(jù)庫對這些隔離級別的支持程度有所不同。
1.4.1 讀未提交(Read Uncommitted)
最低的隔離級別,允許一個事務(wù)讀取另一個事務(wù)尚未提交的修改數(shù)據(jù)。該隔離級別無法解決臟讀、不可重復(fù)讀、幻讀等任何并發(fā)問題,實際開發(fā)中很少使用。
1.4.2 讀已提交(Read Committed)
允許一個事務(wù)讀取另一個事務(wù)已經(jīng)提交的修改數(shù)據(jù)。該隔離級別可以解決臟讀問題,但無法解決不可重復(fù)讀和幻讀問題。這是大多數(shù)數(shù)據(jù)庫的默認隔離級別,如Oracle、SQL Server等。
1.4.3 可重復(fù)讀(Repeatable Read)
保證同一個事務(wù)中,多次讀取同一批數(shù)據(jù)時,得到的結(jié)果一致。該隔離級別可以解決臟讀和不可重復(fù)讀問題,但無法完全解決幻讀問題(MySQL的InnoDB引擎通過MVCC機制在可重復(fù)讀級別下解決了幻讀問題)。這是MySQL的默認隔離級別。
1.4.4 串行化(Serializable)
最高的隔離級別,要求所有事務(wù)串行執(zhí)行,即一個事務(wù)執(zhí)行完成后,另一個事務(wù)才能開始執(zhí)行。該隔離級別可以解決所有并發(fā)問題,但會嚴重影響數(shù)據(jù)庫的并發(fā)性能,適用于數(shù)據(jù)一致性要求極高但并發(fā)量極低的場景。
1.4.5 隔離級別與并發(fā)問題的關(guān)系
為了更清晰地展示隔離級別與并發(fā)問題的對應(yīng)關(guān)系,以下表格進行了總結(jié):
| 隔離級別 | 臟讀 | 不可重復(fù)讀 | 幻讀 | 丟失修改 |
|---|---|---|---|---|
| 讀未提交 | 可能 | 可能 | 可能 | 可能 |
| 讀已提交 | 不可能 | 可能 | 可能 | 可能 |
| 可重復(fù)讀 | 不可能 | 不可能 | MySQL中不可能 | 不可能 |
| 串行化 | 不可能 | 不可能 | 不可能 | 不可能 |
第二章 SpringBoot事務(wù)的核心機制
2.1 Spring事務(wù)管理的核心接口
Spring框架的事務(wù)管理基于一系列核心接口實現(xiàn),這些接口定義了事務(wù)管理的基本規(guī)范,SpringBoot作為Spring的子項目,完全繼承了這些接口的功能,并通過自動配置簡化了其使用。
2.1.1 PlatformTransactionManager
PlatformTransactionManager是Spring事務(wù)管理的核心接口,它定義了事務(wù)的基本操作方法,如獲取事務(wù)、提交事務(wù)、回滾事務(wù)等。該接口是一個抽象層,不同的數(shù)據(jù)庫或持久層框架有其對應(yīng)的實現(xiàn)類,例如:
- DataSourceTransactionManager:基于JDBC數(shù)據(jù)源的事務(wù)管理器,適用于使用JDBC或MyBatis進行數(shù)據(jù)訪問的場景。
- HibernateTransactionManager:基于Hibernate的事務(wù)管理器,適用于使用Hibernate進行數(shù)據(jù)訪問的場景。
- JpaTransactionManager:基于JPA的事務(wù)管理器,適用于使用JPA進行數(shù)據(jù)訪問的場景。
在SpringBoot中,當(dāng)引入spring-boot-starter-jdbc、spring-boot-starter-mybatis等依賴時,SpringBoot會自動配置對應(yīng)的PlatformTransactionManager實現(xiàn)類,開發(fā)者無需手動配置。
2.1.2 TransactionDefinition
TransactionDefinition接口定義了事務(wù)的屬性,如隔離級別、傳播行為、超時時間、是否為只讀事務(wù)等。這些屬性決定了事務(wù)的執(zhí)行規(guī)則,開發(fā)者可以通過配置這些屬性來滿足不同的業(yè)務(wù)需求。
2.1.3 TransactionStatus
TransactionStatus接口用于表示事務(wù)的當(dāng)前狀態(tài),它提供了一系列方法來獲取和修改事務(wù)的狀態(tài),如判斷事務(wù)是否為新事務(wù)、是否已標記為回滾、設(shè)置事務(wù)回滾等。PlatformTransactionManager在執(zhí)行事務(wù)操作時,會通過該接口獲取事務(wù)的狀態(tài)信息,并根據(jù)狀態(tài)執(zhí)行相應(yīng)的操作。
2.2 Spring事務(wù)的傳播行為
事務(wù)的傳播行為是Spring事務(wù)管理中一個非常重要的概念,它定義了當(dāng)一個帶有事務(wù)的方法調(diào)用另一個帶有事務(wù)的方法時,如何處理兩個方法之間的事務(wù)關(guān)系。例如,是沿用當(dāng)前事務(wù),還是創(chuàng)建一個新的事務(wù),或是將當(dāng)前事務(wù)掛起等。Spring定義了7種事務(wù)傳播行為,具體如下:
2.2.1 REQUIRED(默認)
如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前不存在事務(wù),則創(chuàng)建一個新的事務(wù)。這是最常用的傳播行為,適用于大多數(shù)業(yè)務(wù)場景。例如,ServiceA的methodA方法調(diào)用ServiceB的methodB方法,如果methodA已經(jīng)開啟了事務(wù),那么methodB會加入該事務(wù);如果methodA沒有開啟事務(wù),那么methodB會創(chuàng)建一個新的事務(wù)。
2.2.2 SUPPORTS
如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前不存在事務(wù),則以非事務(wù)方式執(zhí)行。該傳播行為適用于那些不需要事務(wù)支持,但如果有事務(wù)也可以加入的方法。例如,一些查詢方法,即使沒有事務(wù)也可以執(zhí)行,但若存在事務(wù)則可以保證數(shù)據(jù)的一致性。
2.2.3 MANDATORY
如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前不存在事務(wù),則拋出異常。該傳播行為強制要求方法必須在事務(wù)中執(zhí)行,適用于那些必須依賴事務(wù)才能保證數(shù)據(jù)一致性的方法。例如,資金轉(zhuǎn)賬的核心方法,必須在事務(wù)中執(zhí)行,否則直接拋出異常。
2.2.4 REQUIRES_NEW
無論當(dāng)前是否存在事務(wù),都創(chuàng)建一個新的事務(wù);如果當(dāng)前存在事務(wù),則將當(dāng)前事務(wù)掛起。該傳播行為適用于那些需要獨立事務(wù)的方法,即使調(diào)用者存在事務(wù),被調(diào)用者的事務(wù)也獨立執(zhí)行,不受調(diào)用者事務(wù)回滾的影響。例如,訂單創(chuàng)建成功后記錄日志的方法,即使訂單創(chuàng)建事務(wù)回滾,日志記錄事務(wù)也應(yīng)該正常提交。
2.2.5 NOT_SUPPORTED
以非事務(wù)方式執(zhí)行;如果當(dāng)前存在事務(wù),則將當(dāng)前事務(wù)掛起。該傳播行為適用于那些不需要事務(wù)支持,且不希望干擾當(dāng)前事務(wù)的方法。例如,一些耗時較長的非核心業(yè)務(wù)操作,如數(shù)據(jù)導(dǎo)出,以非事務(wù)方式執(zhí)行可以提高性能,避免占用事務(wù)資源。
2.2.6 NEVER
以非事務(wù)方式執(zhí)行;如果當(dāng)前存在事務(wù),則拋出異常。該傳播行為強制要求方法必須在非事務(wù)環(huán)境中執(zhí)行,適用于那些絕對不能在事務(wù)中執(zhí)行的方法。例如,某些初始化方法,如果在事務(wù)中執(zhí)行可能會導(dǎo)致數(shù)據(jù)鎖定。
2.2.7 NESTED
如果當(dāng)前存在事務(wù),則在當(dāng)前事務(wù)中創(chuàng)建一個嵌套事務(wù);如果當(dāng)前不存在事務(wù),則創(chuàng)建一個新的事務(wù)。嵌套事務(wù)是當(dāng)前事務(wù)的一個子事務(wù),它依賴于當(dāng)前事務(wù),只有當(dāng)前事務(wù)提交后,嵌套事務(wù)才能提交;如果當(dāng)前事務(wù)回滾,嵌套事務(wù)也會回滾,但嵌套事務(wù)的回滾不會影響當(dāng)前事務(wù)的其他部分。該傳播行為與REQUIRES_NEW的區(qū)別在于,REQUIRES_NEW創(chuàng)建的是完全獨立的事務(wù),而NESTED創(chuàng)建的是依賴于父事務(wù)的子事務(wù)。
2.3 SpringBoot事務(wù)的實現(xiàn)方式
SpringBoot支持兩種事務(wù)管理方式:編程式事務(wù)和聲明式事務(wù)。編程式事務(wù)需要開發(fā)者手動編寫代碼來控制事務(wù)的開啟、提交和回滾,靈活性高但代碼侵入性強;聲明式事務(wù)通過注解或XML配置的方式實現(xiàn)事務(wù)管理,代碼侵入性低,是SpringBoot開發(fā)中推薦使用的方式。
2.3.1 編程式事務(wù)
編程式事務(wù)通過Spring提供的TransactionTemplate或直接使用PlatformTransactionManager來實現(xiàn)。TransactionTemplate是對編程式事務(wù)的封裝,簡化了事務(wù)操作的代碼。
示例代碼如下(基于TransactionTemplate):
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockMapper stockMapper;
@Autowired
private TransactionTemplate transactionTemplate;
public void createOrder(Order order) {
// 使用TransactionTemplate執(zhí)行事務(wù)
transactionTemplate.execute(status -> {
try {
// 扣減庫存
stockMapper.reduceStock(order.getProductId(), order.getQuantity());
// 創(chuàng)建訂單
orderMapper.insertOrder(order);
return true;
} catch (Exception e) {
// 事務(wù)回滾
status.setRollbackOnly();
e.printStackTrace();
return false;
}
});
}
}編程式事務(wù)的優(yōu)點是可以精確控制事務(wù)的邊界和執(zhí)行邏輯,適用于復(fù)雜的事務(wù)場景;缺點是需要在業(yè)務(wù)代碼中嵌入事務(wù)管理代碼,增加了代碼的耦合度。
2.3.2 聲明式事務(wù)
聲明式事務(wù)基于AOP(面向切面編程)實現(xiàn),通過注解或XML配置的方式將事務(wù)管理邏輯與業(yè)務(wù)邏輯分離,開發(fā)者只需在需要事務(wù)支持的方法上添加注解即可實現(xiàn)事務(wù)管理。SpringBoot推薦使用@Transactional注解實現(xiàn)聲明式事務(wù)。
使用聲明式事務(wù)的步驟非常簡單:
- 在SpringBoot啟動類上添加@EnableTransactionManagement注解(SpringBoot 2.0+版本可省略該注解,因為其會自動識別事務(wù)相關(guān)依賴并開啟事務(wù)管理)。
- 在需要事務(wù)支持的Service方法上添加@Transactional注解。
示例代碼如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockMapper stockMapper;
// 添加@Transactional注解開啟事務(wù)
@Transactional
public void createOrder(Order order) {
// 扣減庫存
stockMapper.reduceStock(order.getProductId(), order.getQuantity());
// 創(chuàng)建訂單
orderMapper.insertOrder(order);
// 如果出現(xiàn)異常,事務(wù)會自動回滾
if (order.getQuantity() <= 0) {
throw new IllegalArgumentException("訂單數(shù)量不能小于等于0");
}
}
}聲明式事務(wù)的優(yōu)點是代碼侵入性低,事務(wù)管理邏輯與業(yè)務(wù)邏輯分離,提高了代碼的可讀性和可維護性;缺點是事務(wù)控制的粒度相對較粗,無法精確控制事務(wù)的執(zhí)行邏輯。
第三章 SpringBoot聲明式事務(wù)的使用詳解
3.1 @Transactional注解的核心屬性
@Transactional注解提供了多個屬性,用于配置事務(wù)的傳播行為、隔離級別、超時時間等,開發(fā)者可以根據(jù)業(yè)務(wù)需求靈活配置。以下是常用的核心屬性:
3.1.1 propagation:事務(wù)傳播行為
用于指定事務(wù)的傳播行為,默認值為Propagation.REQUIRED??梢酝ㄟ^Propagation枚舉類指定其他傳播行為,例如:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logOrderCreate(Order order) {
// 記錄訂單創(chuàng)建日志,該方法會創(chuàng)建新的事務(wù)
logMapper.insertOrderLog(order.getId(), "訂單創(chuàng)建成功");
}
3.1.2 isolation:事務(wù)隔離級別
用于指定事務(wù)的隔離級別,默認值為Isolation.DEFAULT,即使用數(shù)據(jù)庫的默認隔離級別??梢酝ㄟ^Isolation枚舉類指定其他隔離級別,例如:
@Transactional(isolation = Isolation.REPEATABLE_READ)
public List<Order> queryOrderByUserId(Long userId) {
// 查詢用戶的訂單,使用可重復(fù)讀隔離級別
return orderMapper.selectByUserId(userId);
}
3.1.3 timeout:事務(wù)超時時間
用于指定事務(wù)的超時時間,單位為秒,默認值為-1,表示不設(shè)置超時時間(即使用數(shù)據(jù)庫的默認超時時間)。如果事務(wù)執(zhí)行時間超過指定的超時時間,事務(wù)會自動回滾。例如:
@Transactional(timeout = 30)
public void batchImportData(List<Data> dataList) {
// 批量導(dǎo)入數(shù)據(jù),事務(wù)超時時間為30秒
dataMapper.batchInsert(dataList);
}
3.1.4 readOnly:是否為只讀事務(wù)
用于指定事務(wù)是否為只讀事務(wù),默認值為false。如果將該屬性設(shè)置為true,數(shù)據(jù)庫會對事務(wù)進行優(yōu)化,提高查詢性能,但此時事務(wù)中不能執(zhí)行修改數(shù)據(jù)的操作(如插入、更新、刪除),否則會拋出異常。該屬性適用于純查詢的業(yè)務(wù)方法,例如:
@Transactional(readOnly = true)
public Order queryOrderById(Long orderId) {
// 純查詢方法,設(shè)置為只讀事務(wù)
return orderMapper.selectById(orderId);
}
3.1.5 rollbackFor:指定需要回滾的異常類型
默認情況下,Spring事務(wù)只在遇到運行時異常(RuntimeException)和錯誤(Error)時才會回滾事務(wù),對于編譯時異常不會回滾。通過rollbackFor屬性可以指定需要回滾的異常類型,例如:
@Transactional(rollbackFor = Exception.class)
public void updateOrderStatus(Long orderId, Integer status) throws Exception {
// 該方法拋出任何Exception類型的異常,事務(wù)都會回滾
Order order = orderMapper.selectById(orderId);
if (order == null) {
throw new Exception("訂單不存在");
}
order.setStatus(status);
orderMapper.update(order);
}
3.1.6 noRollbackFor:指定不需要回滾的異常類型
與rollbackFor屬性相反,noRollbackFor屬性用于指定不需要回滾的異常類型,即使該異常是運行時異常,事務(wù)也不會回滾。例如:
@Transactional(noRollbackFor = BusinessException.class)
public void processOrder(Order order) {
try {
// 業(yè)務(wù)處理邏輯
} catch (BusinessException e) {
// 拋出BusinessException時,事務(wù)不回滾
e.printStackTrace();
}
}
3.2 @Transactional注解的使用位置
@Transactional注解可以用于類上或方法上,其作用范圍有所不同:
3.2.1 用于類上
當(dāng)@Transactional注解用于類上時,該注解會作用于類中的所有公共(public)方法,即類中的所有public方法都會開啟事務(wù)。如果類中的某個方法需要特殊的事務(wù)配置,可以在該方法上單獨添加@Transactional注解,方法上的注解會覆蓋類上的注解配置。例如:
@Service
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public class OrderService {
// 繼承類上的事務(wù)配置
public void createOrder(Order order) {
// 業(yè)務(wù)邏輯
}
// 覆蓋類上的事務(wù)配置,使用REQUIRES_NEW傳播行為
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logOrderCreate(Order order) {
// 業(yè)務(wù)邏輯
}
}3.2.2 用于方法上
當(dāng)@Transactional注解用于方法上時,該注解只作用于當(dāng)前方法。需要注意的是,@Transactional注解只能作用于公共(public)方法,對于非public方法(如private、protected、default修飾的方法),注解不會生效,因為Spring AOP只能代理public方法。
3.3 事務(wù)的嵌套調(diào)用場景分析
在實際開發(fā)中,經(jīng)常會遇到事務(wù)方法嵌套調(diào)用的場景,不同的傳播行為會導(dǎo)致不同的事務(wù)執(zhí)行結(jié)果。以下通過幾個典型場景分析事務(wù)的嵌套調(diào)用邏輯:
3.3.1 場景一:同一Service內(nèi)的事務(wù)方法嵌套
在同一Service類中,帶有事務(wù)的methodA方法調(diào)用帶有事務(wù)的methodB方法,此時由于Spring AOP的代理機制,methodB方法上的@Transactional注解不會生效,事務(wù)的傳播行為由methodA方法的配置決定。例如:
@Service
public class OrderService {
@Transactional(propagation = Propagation.REQUIRED)
public void methodA(Order order) {
// 調(diào)用同一類中的methodB方法
methodB(order);
// 業(yè)務(wù)邏輯
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB(Order order) {
// 業(yè)務(wù)邏輯
}
}上述代碼中,methodA調(diào)用methodB時,由于是內(nèi)部方法調(diào)用,沒有通過Spring的代理對象,因此methodB上的@Transactional注解不會生效,methodB會加入methodA的事務(wù)中,而不會創(chuàng)建新的事務(wù)。如果需要methodB的事務(wù)注解生效,可以通過以下兩種方式解決:
- 將methodB方法提取到另一個Service類中,通過依賴注入的方式調(diào)用。
- 在當(dāng)前Service類中通過@Autowired注入自身的代理對象,使用代理對象調(diào)用methodB方法。
方式二的示例代碼如下:
@Service
public class OrderService {
@Autowired
private OrderService orderService; // 注入自身的代理對象
@Transactional(propagation = Propagation.REQUIRED)
public void methodA(Order order) {
// 使用代理對象調(diào)用methodB方法
orderService.methodB(order);
// 業(yè)務(wù)邏輯
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB(Order order) {
// 業(yè)務(wù)邏輯
}
}3.3.2 場景二:不同Service間的事務(wù)方法嵌套(REQUIRED傳播行為)
ServiceA的methodA方法(REQUIRED傳播行為)調(diào)用ServiceB的methodB方法(REQUIRED傳播行為),此時methodB會加入methodA的事務(wù)中,兩者共用一個事務(wù)。如果methodA或methodB中出現(xiàn)異常,整個事務(wù)會回滾。例如:
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 業(yè)務(wù)邏輯A
serviceB.methodB();
// 如果此處拋出異常,methodA和methodB的操作都會回滾
throw new RuntimeException("methodA異常");
}
}
@Service
public class ServiceB {
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// 業(yè)務(wù)邏輯B
}
}上述代碼中,methodA調(diào)用methodB后拋出異常,由于兩者共用一個事務(wù),因此methodA和methodB的操作都會回滾。
3.3.3 場景三:不同Service間的事務(wù)方法嵌套(REQUIRES_NEW傳播行為)
ServiceA的methodA方法(REQUIRED傳播行為)調(diào)用ServiceB的methodB方法(REQUIRES_NEW傳播行為),此時methodB會創(chuàng)建一個新的事務(wù),與methodA的事務(wù)相互獨立。如果methodA中出現(xiàn)異常,methodA的事務(wù)會回滾,但methodB的事務(wù)不會受影響;如果methodB中出現(xiàn)異常,methodB的事務(wù)會回滾,同時異常會傳播到methodA,導(dǎo)致methodA的事務(wù)也回滾。例如:
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 業(yè)務(wù)邏輯A
serviceB.methodB();
// 如果此處拋出異常,methodA的操作回滾,methodB的操作已提交不會回滾
throw new RuntimeException("methodA異常");
}
}
@Service
public class ServiceB {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// 業(yè)務(wù)邏輯B,執(zhí)行完成后會提交事務(wù)
}
}上述代碼中,methodB執(zhí)行完成后會提交自己的事務(wù),隨后methodA拋出異常,methodA的事務(wù)回滾,但methodB的事務(wù)已經(jīng)提交,因此methodB的操作不會回滾。
第四章 SpringBoot事務(wù)的常見問題與解決方案
4.1 事務(wù)不生效的常見原因
在使用SpringBoot事務(wù)的過程中,經(jīng)常會遇到事務(wù)不生效的問題,即方法執(zhí)行出現(xiàn)異常后,事務(wù)沒有回滾。以下是事務(wù)不生效的常見原因及解決方案:
4.1.1 方法不是public修飾的
Spring AOP只能代理public方法,@Transactional注解只對public方法生效。如果將注解添加到非public方法上,事務(wù)不會生效。
解決方案:將需要事務(wù)支持的方法改為public修飾。
4.1.2 異常類型不匹配
默認情況下,Spring事務(wù)只在遇到RuntimeException和Error時才會回滾,如果方法拋出的是編譯時異常(如IOException、SQLException等),事務(wù)不會回滾。
解決方案:通過@Transactional注解的rollbackFor屬性指定需要回滾的異常類型,例如rollbackFor = Exception.class。
4.1.3 異常被手動捕獲且未重新拋出
如果在事務(wù)方法中手動捕獲了異常,并且沒有將異常重新拋出,Spring無法感知到異常的發(fā)生,因此不會觸發(fā)事務(wù)回滾。例如:
@Transactional
public void createOrder(Order order) {
try {
stockMapper.reduceStock(order.getProductId(), order.getQuantity());
orderMapper.insertOrder(order);
} catch (Exception e) {
// 捕獲異常但未重新拋出,事務(wù)不會回滾
e.printStackTrace();
}
}
解決方案:在catch塊中重新拋出異常,或者通過TransactionStatus的setRollbackOnly()方法手動標記事務(wù)回滾。修改后的代碼如下:
@Transactional
public void createOrder(Order order) {
try {
stockMapper.reduceStock(order.getProductId(), order.getQuantity());
orderMapper.insertOrder(order);
} catch (Exception e) {
e.printStackTrace();
// 重新拋出異常,觸發(fā)事務(wù)回滾
throw new RuntimeException("創(chuàng)建訂單失敗", e);
}
}
4.1.4 同一Service內(nèi)的內(nèi)部方法調(diào)用
如3.3.1節(jié)所述,同一Service類中,事務(wù)方法調(diào)用另一個事務(wù)方法時,由于是內(nèi)部方法調(diào)用,沒有通過Spring的代理對象,因此被調(diào)用方法上的@Transactional注解不會生效。
解決方案:將被調(diào)用的事務(wù)方法提取到另一個Service類中,或者在當(dāng)前Service類中注入自身的代理對象進行調(diào)用。
4.1.5 數(shù)據(jù)源未配置事務(wù)管理器
SpringBoot需要為數(shù)據(jù)源配置對應(yīng)的事務(wù)管理器(如DataSourceTransactionManager)才能實現(xiàn)事務(wù)管理。如果數(shù)據(jù)源沒有配置事務(wù)管理器,事務(wù)不會生效。
解決方案:確保項目中引入了對應(yīng)的數(shù)據(jù)源依賴(如spring-boot-starter-jdbc),SpringBoot會自動配置事務(wù)管理器。如果是自定義數(shù)據(jù)源,需要手動配置事務(wù)管理器,示例代碼如下:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
@Configuration
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}4.1.6 事務(wù)傳播行為配置錯誤
如果事務(wù)傳播行為配置不當(dāng),也可能導(dǎo)致事務(wù)不生效。例如,將傳播行為配置為NOT_SUPPORTED或NEVER,會導(dǎo)致方法以非事務(wù)方式執(zhí)行。
解決方案:根據(jù)業(yè)務(wù)場景選擇合適的事務(wù)傳播行為,大多數(shù)場景下使用默認的REQUIRED即可。
4.2 事務(wù)回滾異常的處理技巧
在實際開發(fā)中,有時需要根據(jù)不同的業(yè)務(wù)場景靈活控制事務(wù)的回滾邏輯,以下是一些事務(wù)回滾異常的處理技巧:
4.2.1 手動標記事務(wù)回滾
如果需要在不拋出異常的情況下手動觸發(fā)事務(wù)回滾,可以通過TransactionStatus對象的setRollbackOnly()方法實現(xiàn)。例如,在某些業(yè)務(wù)場景下,雖然沒有發(fā)生異常,但根據(jù)業(yè)務(wù)規(guī)則需要回滾事務(wù):
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionStatus;
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
// 獲取當(dāng)前事務(wù)狀態(tài)
TransactionStatus status = TransactionSynchronizationManager.getCurrentTransactionStatus();
// 業(yè)務(wù)邏輯處理
boolean isSuccess = checkBusinessRule(order);
if (!isSuccess) {
// 手動標記事務(wù)回滾
status.setRollbackOnly();
return;
}
stockMapper.reduceStock(order.getProductId(), order.getQuantity());
orderMapper.insertOrder(order);
}
private boolean checkBusinessRule(Order order) {
// 業(yè)務(wù)規(guī)則校驗
return order.getAmount() > 0;
}
}4.2.2 自定義異常與事務(wù)回滾
在實際開發(fā)中,通常會定義自定義的業(yè)務(wù)異常來表示不同的業(yè)務(wù)錯誤。為了讓自定義異常能夠觸發(fā)事務(wù)回滾,需要在@Transactional注解的rollbackFor屬性中指定自定義異常類型,或者讓自定義異常繼承RuntimeException(因為Spring默認對RuntimeException回滾)。
示例代碼如下(自定義異常繼承RuntimeException):
// 自定義業(yè)務(wù)異常
public class BusinessException extends RuntimeException {
public BusinessException(String message) {
super(message);
}
}
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
if (order.getQuantity() <= 0) {
// 拋出自定義異常,觸發(fā)事務(wù)回滾
throw new BusinessException("訂單數(shù)量不能小于等于0");
}
stockMapper.reduceStock(order.getProductId(), order.getQuantity());
orderMapper.insertOrder(order);
}
}4.3 高并發(fā)場景下的事務(wù)優(yōu)化
在高并發(fā)場景下,事務(wù)的使用不當(dāng)可能會導(dǎo)致數(shù)據(jù)庫性能下降、死鎖等問題,以下是一些高并發(fā)場景下的事務(wù)優(yōu)化技巧:
4.3.1 縮小事務(wù)范圍
事務(wù)的執(zhí)行時間越長,占用的數(shù)據(jù)庫資源越多,并發(fā)性能越低,同時出現(xiàn)死鎖的風(fēng)險也越高。因此,應(yīng)盡量縮小事務(wù)的范圍,只將核心的數(shù)據(jù)庫操作包含在事務(wù)中,避免在事務(wù)中執(zhí)行耗時操作(如網(wǎng)絡(luò)請求、文件IO、復(fù)雜計算等)。
優(yōu)化前的代碼(事務(wù)中包含耗時操作):
@Transactional
public void createOrder(Order order) {
// 耗時的網(wǎng)絡(luò)請求(不應(yīng)包含在事務(wù)中)
User user = userFeignClient.getUserById(order.getUserId());
if (user == null) {
throw new BusinessException("用戶不存在");
}
// 核心數(shù)據(jù)庫操作
stockMapper.reduceStock(order.getProductId(), order.getQuantity());
orderMapper.insertOrder(order);
}
優(yōu)化后的代碼(將耗時操作移出事務(wù)):
public void createOrder(Order order) {
// 耗時的網(wǎng)絡(luò)請求(移出事務(wù))
User user = userFeignClient.getUserById(order.getUserId());
if (user == null) {
throw new BusinessException("用戶不存在");
}
// 調(diào)用事務(wù)方法執(zhí)行核心數(shù)據(jù)庫操作
doCreateOrder(order);
}
@Transactional
public void doCreateOrder(Order order) {
// 核心數(shù)據(jù)庫操作
stockMapper.reduceStock(order.getProductId(), order.getQuantity());
orderMapper.insertOrder(order);
}4.3.2 使用合適的隔離級別
隔離級別越高,數(shù)據(jù)一致性越好,但并發(fā)性能越低。在高并發(fā)場景下,應(yīng)在保證數(shù)據(jù)一致性的前提下,盡量選擇較低的隔離級別。例如,大多數(shù)業(yè)務(wù)場景下使用讀已提交(Read Committed)隔離級別即可,既能避免臟讀,又能保證較好的并發(fā)性能。
配置示例:
@Transactional(isolation = Isolation.READ_COMMITTED)
public void createOrder(Order order) {
// 業(yè)務(wù)邏輯
}
4.3.3 避免事務(wù)中的鎖競爭
在高并發(fā)場景下,事務(wù)中的鎖競爭是導(dǎo)致性能下降的主要原因之一。為了避免鎖競爭,可以采取以下措施:
- 盡量使用行鎖,避免使用表鎖。例如,在執(zhí)行更新操作時,盡量通過主鍵或唯一索引定位數(shù)據(jù),避免全表掃描導(dǎo)致的表鎖。
- 控制事務(wù)的執(zhí)行順序,避免不同事務(wù)交叉更新同一批數(shù)據(jù)導(dǎo)致死鎖。例如,多個事務(wù)都需要更新A和B兩條數(shù)據(jù)時,都按照先更新A再更新B的順序執(zhí)行。
- 使用樂觀鎖替代悲觀鎖。樂觀鎖通過版本號或時間戳機制實現(xiàn),不需要在事務(wù)中持有鎖,適用于讀多寫少的高并發(fā)場景。
樂觀鎖示例(基于版本號):
// 實體類添加版本號字段
public class Stock {
private Long id;
private Long productId;
private Integer quantity;
private Integer version; // 版本號字段
// getter和setter
}
// Mapper接口
public interface StockMapper {
// 樂觀鎖更新:只有版本號匹配時才更新
int reduceStockWithVersion(@Param("productId") Long productId,
@Param("quantity") Integer quantity,
@Param("version") Integer version);
}
// XML映射文件
<update id="reduceStockWithVersion">
UPDATE stock
SET quantity = quantity - #{quantity}, version = version + 1
WHERE product_id = #{productId} AND version = #{version}
</update>
// Service方法
@Transactional
public void reduceStock(Long productId, Integer quantity) {
// 查詢庫存和版本號
Stock stock = stockMapper.selectByProductId(productId);
if (stock == null || stock.getQuantity() < quantity) {
throw new BusinessException("庫存不足");
}
// 樂觀鎖更新
int rows = stockMapper.reduceStockWithVersion(productId, quantity, stock.getVersion());
if (rows == 0) {
// 更新失敗,說明庫存已被其他事務(wù)修改,拋出異?;蛑卦?
throw new BusinessException("庫存更新失敗,請重試");
}
}4.3.4 使用事務(wù)超時機制
在高并發(fā)場景下,部分事務(wù)可能由于資源競爭等原因?qū)е聢?zhí)行時間過長,占用大量數(shù)據(jù)庫資源。通過設(shè)置事務(wù)超時時間,可以讓長時間未執(zhí)行完成的事務(wù)自動回滾,釋放數(shù)據(jù)庫資源。
配置示例:
@Transactional(timeout = 10) // 事務(wù)超時時間為10秒
public void batchProcessData(List<Data> dataList) {
// 批量處理數(shù)據(jù)的業(yè)務(wù)邏輯
}
第五章 總結(jié)與拓展
5.1 文章知識點總結(jié)
本文圍繞SpringBoot事務(wù)展開,從基礎(chǔ)理論到實踐應(yīng)用進行了全面講解,核心知識點總結(jié)如下:事務(wù)基礎(chǔ)層面,明確了事務(wù)的定義及ACID四大核心特性,深入分析了臟讀、不可重復(fù)讀等并發(fā)問題及對應(yīng)的數(shù)據(jù)庫隔離級別,為后續(xù)SpringBoot事務(wù)的學(xué)習(xí)奠定理論基礎(chǔ);核心機制層面,剖析了Spring事務(wù)管理的三大核心接口,詳細解讀了7種事務(wù)傳播行為的適用場景,對比了編程式與聲明式兩種事務(wù)實現(xiàn)方式的優(yōu)劣;使用詳解層面,聚焦聲明式事務(wù)的@Transactional注解,梳理了核心屬性的配置方法、注解使用位置及嵌套調(diào)用場景;問題解決層面,歸納了事務(wù)不生效的常見原因及解決方案,提供了事務(wù)回滾異常的處理技巧,并給出高并發(fā)場景下的事務(wù)優(yōu)化策略。通過理論與代碼示例結(jié)合的方式,構(gòu)建了完整的SpringBoot事務(wù)知識體系。
5.2 知識拓展與延伸
除了本文講解的核心內(nèi)容,SpringBoot事務(wù)還有一些進階知識點值得關(guān)注。例如,分布式事務(wù)問題,在微服務(wù)架構(gòu)中,跨服務(wù)的事務(wù)操作無法依賴單一數(shù)據(jù)庫的事務(wù)機制,此時需要引入Seata、Saga等分布式事務(wù)解決方案;又如,事務(wù)同步機制,Spring提供的TransactionSynchronization接口可以在事務(wù)提交或回滾的不同階段執(zhí)行自定義邏輯,適用于事務(wù)完成后發(fā)送消息、更新緩存等場景。此外,SpringBoot 3.x版本中對事務(wù)管理的自動配置進行了優(yōu)化,結(jié)合Jakarta EE的規(guī)范調(diào)整了相關(guān)API,開發(fā)者在升級框架時需注意適配。
5.3 推薦閱讀資料
為幫助大家進一步深化對SpringBoot事務(wù)及相關(guān)技術(shù)的理解,推薦以下優(yōu)質(zhì)資料:
- 官方文檔:Spring Framework官方文檔的“Transaction Management”章節(jié),是最權(quán)威的學(xué)習(xí)資料,詳細闡述了Spring事務(wù)的設(shè)計理念與實現(xiàn)細節(jié)
到此這篇關(guān)于SpringBoot 事務(wù)深度解析之從理論到實踐的完整指南的文章就介紹到這了,更多相關(guān)SpringBoot 事務(wù)全解析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot 日志配置詳解:log4j2.xml 的完整配置指南
本文介紹了在SpringBoot項目中配置Log4j2的方法,包括選擇Log4j2的原因、Maven依賴配置、application.yml配置、log4j2.xml完整配置詳解,以及性能優(yōu)化建議,通過合理配置,可以構(gòu)建高性能的異步日志記錄系統(tǒng),滿足日常開發(fā)和運維需求2026-04-04
Java transient關(guān)鍵字與序列化操作實例詳解
這篇文章主要介紹了Java transient關(guān)鍵字與序列化操作,結(jié)合實例形式詳細分析了java序列化操作相關(guān)實現(xiàn)方法與操作注意事項,需要的朋友可以參考下2019-09-09
SpringBoot獲取Request和Response方法代碼解析
這篇文章主要介紹了SpringBoot獲取Request和Response方法代碼解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-11-11
解決Mybatis在IDEA中找不到mapper映射文件的問題
這篇文章主要介紹了解決Mybatis在IDEA中找不到mapper映射文件的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10
java操作mongodb基礎(chǔ)(查詢 排序 輸出list)
java操作mongodb基礎(chǔ)學(xué)習(xí)查詢,排序,limit,輸出為list實例,大家參考使用吧2013-12-12

