Spring實現(xiàn)方式、隔離級別與傳播機制全攻略
在后端開發(fā)中,數(shù)據一致性是核心訴求之一。無論是轉賬時的金額流轉,還是秒殺場景的庫存扣減,稍有不慎就會導致數(shù)據錯亂 —— 比如 A 賬戶扣款成功但 B 賬戶未到賬,下單成功但庫存未減少。而事務,正是解決這類問題的關鍵技術。Spring 框架對事務進行了高度封裝,提供了靈活易用的事務管理能力。本文將從事務基礎出發(fā),深入拆解 Spring 事務的實現(xiàn)方式、核心注解配置、隔離級別,并重點剖析事務傳播機制的 7 種行為與實際應用場景,幫你徹底掌握 Spring 事務的核心用法。
一、事務基礎回顧:是什么?為什么需要?
在學習 Spring 事務之前,我們先回顧數(shù)據庫事務的核心概念,這是理解 Spring 事務的基礎。
1.1 事務的定義
事務是一組不可分割的數(shù)據庫操作集合,這組操作要么全部成功執(zhí)行并提交,要么全部失敗并回滾,不存在 “部分成功” 的中間狀態(tài)。就像快遞發(fā)貨,下單、扣庫存、生成物流單這一系列操作,必須同時完成才算交易成功,任何一步失敗都要回到初始狀態(tài)。
1.2 為什么需要事務?
事務的核心價值是保證數(shù)據一致性,我們通過兩個經典場景理解:
- 轉賬場景:A 賬戶轉出 100 元,B 賬戶轉入 100 元。如果沒有事務,A 賬戶扣款成功后,B 賬戶轉入操作失敗,會導致 100 元 “憑空消失”;
- 秒殺場景:用戶下單成功后,需要扣減對應商品庫存。如果下單成功但庫存扣減失敗,會導致超賣(實際庫存為 0 但仍有訂單生成)。
事務通過 “原子性” 特性,確保這一系列操作要么全成,要么全敗,從根本上避免數(shù)據不一致。
1.3 事務的核心操作
數(shù)據庫層面,事務的操作有三步,Spring 事務本質也是對這三步的封裝:
- 開啟事務:
start transaction / begin(操作執(zhí)行前開啟); - 提交事務:
commit(所有操作無異常時提交,數(shù)據永久生效); - 回滾事務:
rollback(任意操作異常時回滾,恢復到操作前狀態(tài))。
二、Spring 事務的兩種實現(xiàn)方式
Spring 支持兩種事務管理方式:編程式事務(手動控制)和聲明式事務(注解自動控制)。實際開發(fā)中,聲明式事務因簡潔高效成為主流,編程式事務僅用于特殊場景。
2.1 編程式事務:手動控制事務生命周期
編程式事務需要開發(fā)者手動編寫代碼開啟、提交、回滾事務,靈活性高但代碼繁瑣。
核心依賴與組件
SpringBoot 內置了DataSourceTransactionManager(事務管理器),無需額外引入依賴,核心組件:
DataSourceTransactionManager:負責事務的開啟、提交、回滾;TransactionDefinition:定義事務屬性(如隔離級別、傳播機制);TransactionStatus:事務的當前狀態(tài)(如是否活躍、是否需要回滾)。
代碼實現(xiàn)(以用戶注冊為例)
- 準備工作:創(chuàng)建數(shù)據庫表(用戶表
user_info、日志表log_info)、實體類、Mapper 接口(文檔中已提供,此處省略); - Controller 層手動控制事務:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
// 注入事務管理器
@Autowired
private DataSourceTransactionManager transactionManager;
// 注入事務屬性定義
@Autowired
private TransactionDefinition transactionDefinition;
// 注入業(yè)務層
@Autowired
private UserService userService;
@RequestMapping("/registry")
public String registry(String name, String password) {
// 1. 開啟事務
TransactionStatus status = transactionManager.getTransaction(transactionDefinition);
try {
// 2. 執(zhí)行核心業(yè)務(用戶注冊)
userService.registryUser(name, password);
// 3. 無異常,提交事務
transactionManager.commit(status);
return "注冊成功";
} catch (Exception e) {
// 4. 有異常,回滾事務
transactionManager.rollback(status);
return "注冊失敗";
}
}
}優(yōu)缺點
- 優(yōu)點:完全手動控制事務邊界,靈活處理復雜場景;
- 缺點:代碼冗余,事務邏輯與業(yè)務邏輯耦合,不利于維護。
2.2 聲明式事務:@Transactional 注解一鍵搞定
聲明式事務基于 AOP 實現(xiàn),通過@Transactional注解自動完成事務的開啟、提交、回滾,無需編寫額外事務代碼,是 Spring 事務的推薦用法。
實現(xiàn)步驟
- 引入依賴(SpringBoot 項目已內置
spring-tx,無需手動引入):
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>- 在需要事務的方法 / 類上添加
@Transactional注解:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/trans")
public class TransactionalController {
@Autowired
private UserService userService;
// 添加入注解,該方法自動支持事務
@Transactional
@RequestMapping("/registry")
public String registry(String name, String password) {
// 執(zhí)行核心業(yè)務
userService.registryUser(name, password);
// 模擬異常(測試回滾)
int a = 10 / 0;
return "注冊成功";
}
}核心原理
- 當方法被
@Transactional修飾時,Spring 會通過 AOP 動態(tài)生成代理對象; - 方法執(zhí)行前,代理對象自動開啟事務;
- 方法執(zhí)行無異常時,自動提交事務;
- 方法拋出未捕獲的異常時,自動回滾事務。
注意事項
@Transactional僅對public方法生效(修飾非 public 方法時不報錯但無事務效果);- 若異常被
try-catch捕獲且未重新拋出,事務不會回滾(Spring 無法感知異常); - 建議在業(yè)務層(Service) 使用該注解(業(yè)務層通常包含多個數(shù)據操作,便于控制事務邊界)。
異常捕獲后的回滾方案
如果需要捕獲異常且讓事務回滾,有兩種方式:
- 重新拋出異常:
@Transactional
public String registry(String name, String password) {
try {
userService.registryUser(name, password);
int a = 10 / 0;
} catch (Exception e) {
e.printStackTrace();
// 重新拋出異常,觸發(fā)回滾
throw e;
}
return "注冊成功";
}
- 手動觸發(fā)回滾:
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Transactional
public String registry(String name, String password) {
try {
userService.registryUser(name, password);
int a = 10 / 0;
} catch (Exception e) {
e.printStackTrace();
// 手動回滾事務
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return "注冊成功";
}三、@Transactional 注解詳解:三大核心屬性
@Transactional注解提供了多個屬性,用于靈活配置事務行為,核心屬性有 3 個:rollbackFor(異常回滾規(guī)則)、isolation(隔離級別)、propagation(傳播機制)。
3.1 rollbackFor:指定回滾的異常類型
默認行為
Spring 事務默認僅對運行時異常(RuntimeException)和 Error 回滾,對非運行時異常(如IOException、SQLException)不回滾。
示例驗證:
@Transactional
@RequestMapping("/r2")
public String r2(String name, String password) throws IOException {
userService.registryUser(name, password);
// 拋出非運行時異常(IOException)
throw new IOException();
}
運行結果:事務未回滾,用戶數(shù)據成功插入數(shù)據庫。
配置 rollbackFor
若需要對所有異常都回滾,或指定特定異?;貪L,通過rollbackFor配置:
// 對所有Exception子類都回滾
@Transactional(rollbackFor = Exception.class)
@RequestMapping("/r2")
public String r2(String name, String password) throws IOException {
userService.registryUser(name, password);
throw new IOException();
}
運行結果:事務回滾,用戶數(shù)據未插入。
擴展用法
- 指定多個異常類型:
@Transactional(rollbackFor = {IOException.class, SQLException.class}); - 反向配置(不回滾的異常):
noRollbackFor = XXXException.class(慎用,可能導致數(shù)據不一致)。
3.2 isolation:事務隔離級別
事務隔離級別解決的是 “多個事務同時操作同一批數(shù)據時的并發(fā)問題”,主要有三類并發(fā)問題:
- 臟讀:一個事務讀取到另一個事務未提交的數(shù)據(可能回滾,導致讀取的數(shù)據無效);
- 不可重復讀:同一事務內多次查詢同一數(shù)據,結果不一致(其他事務修改并提交了該數(shù)據);
- 幻讀:同一事務內多次執(zhí)行同一查詢,返回的結果集行數(shù)不一致(其他事務新增 / 刪除了數(shù)據)。
3.2.1 MySQL 的四種隔離級別(SQL 標準)
MySQL 支持 SQL 標準定義的四種隔離級別,默認隔離級別為可重復讀(REPEATABLE READ):
| 隔離級別 | 臟讀 | 不可重復讀 | 幻讀 | 說明 |
|---|---|---|---|---|
| 讀未提交(READ UNCOMMITTED) | ? | ? | ? | 最低級別,允許讀取未提交數(shù)據,性能最高但一致性最差 |
| 讀已提交(READ COMMITTED) | ? | ? | ? | 只能讀取已提交數(shù)據,避免臟讀,Oracle 默認級別 |
| 可重復讀(REPEATABLE READ) | ? | ? | ? | 同一事務內多次查詢結果一致,避免臟讀和不可重復讀,MySQL 默認級別 |
| 串行化(SERIALIZABLE) | ? | ? | ? | 最高級別,事務串行執(zhí)行,完全避免并發(fā)問題,但性能最差 |
3.2.2 Spring 的五種隔離級別
Spring 在 MySQL 隔離級別的基礎上,增加了DEFAULT(默認值),即沿用數(shù)據庫的隔離級別:
Isolation.DEFAULT:默認值,使用數(shù)據庫的隔離級別(MySQL 為 REPEATABLE READ);Isolation.READ_UNCOMMITTED:對應 MySQL 的讀未提交;Isolation.READ_COMMITTED:對應 MySQL 的讀已提交;Isolation.REPEATABLE_READ:對應 MySQL 的可重復讀;Isolation.SERIALIZABLE:對應 MySQL 的串行化。
配置方式
// 設置隔離級別為讀已提交
@Transactional(isolation = Isolation.READ_COMMITTED)
public void registryUser(String name, String password) {
userInfoMapper.insert(name, password);
}
選擇建議
- 絕大多數(shù)場景:使用默認隔離級別(REPEATABLE READ),兼顧一致性和性能;
- 高一致性要求(如金融場景):使用
SERIALIZABLE,但需注意性能損耗; - 低一致性要求(如日志統(tǒng)計):可使用
READ_COMMITTED,提升并發(fā)性能。
3.3 propagation:事務傳播機制(核心重點)
當多個被@Transactional修飾的方法相互調用時,事務如何在方法間傳遞,這就是傳播機制。比如:方法 A(有事務)調用方法 B(有事務),B 是加入 A 的事務,還是新建獨立事務?
傳播機制的 7 種行為
Spring 定義了 7 種傳播行為,通過@Transactional(propagation = 傳播行為)配置,核心常用的是前 3 種:
| 傳播行為 | 中文說明 | 核心邏輯 | 通俗比喻(結婚買房) |
|---|---|---|---|
| REQUIRED(默認) | 必須有事務 | 若當前存在事務,加入該事務;若不存在,新建事務 | 結婚必須有房:你有房就一起住,沒房就一起買 |
| REQUIRES_NEW | 新建獨立事務 | 無論當前是否有事務,都新建獨立事務,掛起當前事務 | 必須買新房:不管你有沒有房,都要重新買一套,各自獨立 |
| NESTED | 嵌套事務 | 若當前有事務,創(chuàng)建嵌套事務(依賴保存點);若不存在,新建事務 | 以房為基礎:你有房就用你的房,在房里搞 “小項目”;沒房就一起買 |
| SUPPORTS | 支持事務 | 若當前有事務,加入;若沒有,以非事務方式運行 | 可有可無:你有房就一起住,沒房就租房 |
| MANDATORY | 強制事務 | 若當前有事務,加入;若沒有,拋出異常 | 必須有房才結婚:沒房就不結 |
| NOT_SUPPORTED | 不支持事務 | 以非事務方式運行,若當前有事務,掛起事務 | 不需要房:不管你有沒有房,我都租房住 |
| NEVER | 禁止事務 | 以非事務方式運行,若當前有事務,拋出異常 | 不能有房:你有房就不結婚 |
四、事務傳播機制場景演示:實戰(zhàn)理解核心行為
我們通過 “用戶注冊 + 記錄操作日志” 的場景,演示 3 種核心傳播行為的差異(用戶注冊和記錄日志都是帶事務的方法)。
準備工作
- 業(yè)務邏輯:用戶注冊(
UserService.registryUser)后,記錄操作日志(LogService.insertLog); - 模擬異常:在日志記錄方法中拋出
10/0的運行時異常。
4.1 REQUIRED(默認):加入當前事務
代碼配置
// UserService
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void registryUser(String name, String password) {
userInfoMapper.insert(name, password); // 插入用戶
}
}
// LogService
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void insertLog(String name, String op) {
int a = 10 / 0; // 模擬異常
logInfoMapper.insertLog(name, op); // 插入日志
}
}
// Controller
@RestController
@RequestMapping("/propaga")
public class PropagationController {
@Autowired
private UserService userService;
@Autowired
private LogService logService;
@Transactional(propagation = Propagation.REQUIRED)
@RequestMapping("/p1")
public String p1(String name, String password) {
userService.registryUser(name, password); // 調用用戶注冊
logService.insertLog(name, "用戶注冊"); // 調用日志記錄
return "操作成功";
}
}執(zhí)行結果
數(shù)據庫中無用戶數(shù)據和日志數(shù)據插入。
流程分析
- Controller 的
p1方法開啟事務; userService.registryUser加入p1的事務,插入用戶數(shù)據成功;logService.insertLog加入p1的事務,拋出異常;- 由于所有操作在同一個事務中,異常觸發(fā)整體回滾,用戶數(shù)據和日志數(shù)據均回滾。
4.2 REQUIRES_NEW:新建獨立事務
代碼配置
將兩個 Service 方法的傳播機制改為REQUIRES_NEW:
// UserService
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void registryUser(String name, String password) {
userInfoMapper.insert(name, password);
}
// LogService
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertLog(String name, String op) {
int a = 10 / 0;
logInfoMapper.insertLog(name, op);
}執(zhí)行結果
數(shù)據庫中用戶數(shù)據插入成功,日志數(shù)據未插入。
流程分析
- Controller 的
p1方法開啟事務; userService.registryUser新建獨立事務,插入用戶數(shù)據后提交事務(不受后續(xù)異常影響);logService.insertLog新建獨立事務,拋出異常,該事務回滾(日志數(shù)據未插入);- 兩個事務相互獨立,日志事務的異常不影響用戶事務。
4.3 NESTED:嵌套事務(支持局部回滾)
代碼配置
將兩個 Service 方法的傳播機制改為NESTED:
// UserService
@Transactional(propagation = Propagation.NESTED)
public void registryUser(String name, String password) {
userInfoMapper.insert(name, password);
}
// LogService
@Transactional(propagation = Propagation.NESTED)
public void insertLog(String name, String op) {
int a = 10 / 0;
logInfoMapper.insertLog(name, op);
}執(zhí)行結果(未捕獲異常)
數(shù)據庫中無用戶數(shù)據和日志數(shù)據插入。
流程分析
- Controller 的
p1方法開啟事務(父事務); userService.registryUser創(chuàng)建嵌套事務(子事務 1),插入用戶數(shù)據;logService.insertLog創(chuàng)建嵌套事務(子事務 2),拋出異常,子事務 2 回滾;- 由于子事務 2 未捕獲異常,異常向上傳播,父事務回滾,子事務 1 也隨之回滾。
局部回滾場景(捕獲異常)
修改LogService,捕獲異常并手動回滾當前嵌套事務:
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.NESTED)
public void insertLog(String name, String op) {
try {
int a = 10 / 0;
logInfoMapper.insertLog(name, op);
} catch (Exception e) {
e.printStackTrace();
// 手動回滾當前嵌套事務
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
}執(zhí)行結果
數(shù)據庫中用戶數(shù)據插入成功,日志數(shù)據未插入。
核心原理:保存點(Savepoint)
嵌套事務的局部回滾依賴數(shù)據庫的保存點(Savepoint) 機制:
- 父事務開啟后,每個嵌套事務執(zhí)行前會創(chuàng)建一個保存點;
- 嵌套事務回滾時,僅回滾到當前保存點,不影響父事務和其他嵌套事務的已執(zhí)行操作;
- 父事務回滾時,會回滾所有嵌套事務(包括已提交的嵌套事務)。
4.4 NESTED vs REQUIRED:關鍵區(qū)別
| 對比維度 | REQUIRED | NESTED |
|---|---|---|
| 事務關系 | 同一事務 | 父 - 子嵌套事務(保存點隔離) |
| 回滾范圍 | 要么全回滾,要么全提交 | 支持局部回滾(子事務回滾不影響父事務) |
| 異常傳播 | 子事務異常直接導致整體回滾 | 子事務異常可捕獲,僅回滾當前子事務 |
| 適用場景 | 多個操作必須同時成功 / 失?。ㄈ甾D賬) | 多個操作可獨立回滾(如注冊 + 送積分,積分失敗不影響注冊) |
五、Spring 事務常見問題與避坑指南
5.1 @Transactional 注解不生效的場景
- 修飾非 public 方法(如
private、protected); - 異常被
try-catch捕獲且未重新拋出; - 數(shù)據源未配置事務管理器(SpringBoot 自動配置,手動配置時需注意);
- 同一個類中無事務方法調用有事務方法(AOP 無法攔截內部調用);
@Service public class UserService { // 無事務方法 public void test() { registryUser("admin", "123456"); // 內部調用,@Transactional不生效 } @Transactional public void registryUser(String name, String password) { userInfoMapper.insert(name, password); } } - 事務管理器配置錯誤(如多數(shù)據源時未指定對應事務管理器)。
5.2 性能優(yōu)化建議
- 避免大事務:事務范圍越小越好,不要在事務中執(zhí)行非數(shù)據庫操作(如調用第三方接口、文件 IO);
- 合理選擇隔離級別:非核心場景避免使用
SERIALIZABLE,減少鎖競爭; - 傳播機制按需選擇:無需獨立事務時用
REQUIRED,需獨立事務時用REQUIRES_NEW,避免過度使用REQUIRES_NEW導致事務過多; - 避免長事務:長事務會占用數(shù)據庫連接,導致連接池耗盡,影響系統(tǒng)并發(fā)能力。
六、總結
Spring 事務是保證數(shù)據一致性的核心技術,本文從基礎到實戰(zhàn),全面解析了 Spring 事務的核心知識點:
- 事務的本質是 “原子性操作集合”,解決數(shù)據一致性問題;
- Spring 事務有兩種實現(xiàn)方式:編程式(靈活但繁瑣)和聲明式(
@Transactional注解,推薦); @Transactional的三大核心屬性:rollbackFor(控制回滾異常)、isolation(控制并發(fā)問題)、propagation(控制事務傳播);- 事務傳播機制是重點,
REQUIRED(默認)、REQUIRES_NEW(獨立事務)、NESTED(局部回滾)是高頻使用場景; - 嵌套事務通過保存點機制實現(xiàn)局部回滾,適用于 “部分操作可獨立失敗” 的場景。
實際開發(fā)中,建議優(yōu)先使用聲明式事務,根據業(yè)務場景靈活配置隔離級別和傳播機制:
- 轉賬、支付等核心場景:用
REQUIRED隔離級別,確保操作原子性; - 注冊 + 日志、下單 + 庫存等場景:用
NESTED或REQUIRES_NEW,實現(xiàn)部分操作獨立回滾; - 非核心查詢場景:用
READ_COMMITTED隔離級別,提升并發(fā)性能。
到此這篇關于Spring實現(xiàn)方式、隔離級別與傳播機制全攻略的文章就介紹到這了,更多相關spring隔離級別與傳播機制內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot定制三種錯誤頁面及錯誤數(shù)據方法示例
Spring Boot提供的默認異常處理機制通常并不一定適合我們實際的業(yè)務場景,因此,我們通常會根據自身的需要對Spring Boot全局異常進行統(tǒng)一定制,例如定制錯誤頁面,定制錯誤數(shù)據等。本文主要介紹了SpringBoot三種自定義錯誤頁面的實現(xiàn),快來學習吧2021-12-12
Java中使用Thread類和Runnable接口實現(xiàn)多線程的區(qū)別
這篇文章主要介紹了使用Thread類和Runnable接口實現(xiàn)多線程的區(qū)別,本文給大家介紹了兩種實現(xiàn)方式的步驟,除了以上兩種多線程實現(xiàn)方式,還可以使用 Callable 接口實現(xiàn),本文通過實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2022-07-07
java返回前端樹形結構數(shù)據的2種實現(xiàn)方式
近期項目有個需求,需要將組織機構數(shù)據拼成樹型結構返回至前端,下面這篇文章主要給大家介紹了關于java返回前端樹形結構數(shù)據的2種實現(xiàn)方式,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2024-05-05
Spring Boot集成/輸出/日志級別控制/持久化開發(fā)實踐
SpringBoot默認集成Logback,支持靈活日志級別配置(INFO/DEBUG等),輸出包含時間戳、級別、類名等信息,并可通過Slf4j注解簡化日志記錄,實現(xiàn)控制臺與文件持久化存儲,本文給大家介紹Spring Boot集成/輸出/日志級別控制/持久化開發(fā)實踐,感興趣的朋友一起看看吧2025-08-08

