Spring之事務使用指南
- 一、事務的基礎概念
- 1.1 什么是事務?
- 1.2 事務的ACID特性
- 1.3 Spring事務的核心優勢
- 二、Spring事務的核心配置
- 三、事務傳播行為(Propagation)
- 3.1 常用傳播行為詳解
- 3.1.1 `REQUIRED`(默認值)
- 3.1.2 `SUPPORTS`
- 3.1.3 `REQUIRES_NEW`
- 3.1.4 `NEVER`
- 3.1.5 `MANDATORY`
- 3.2 傳播行為選擇原則
- 四、事務隔離級別(Isolation)
- 4.1 并發事務的三大問題
- 4.2 隔離級別詳解
- 4.3 配置隔離級別
- 五、聲明式事務實戰
- 5.1 環境準備
- 5.2 業務實現
- 5.2.1 Mapper接口(MyBatis)
- 5.2.2 Service實現
- 5.3 測試與驗證
- 5.3.1 正常流程(無異常)
- 5.3.2 異常流程(觸發回滾)
- 六、常見問題與避坑指南
- 6.1 事務不回滾(@Transactional失效)
- 6.1.1 異常類型不匹配
- 6.1.2 方法非public
- 6.1.3 自調用導致事務失效
- 6.2 事務超時(Timeout)
- 6.3 只讀事務(readOnly)
事務是保證數據一致性的核心機制,尤其在多步操作的業務場景(如訂單創建同時扣減庫存)中不可或缺,Spring通過AOP實現了聲明式事務管理,簡化了傳統JDBC手動控制事務的繁瑣流程。
一、事務的基礎概念
1.1 什么是事務?
事務(Transaction)是由一系列數據庫操作組成的邏輯單元,這些操作要么全部成功,要么全部失敗(如“創建訂單”需同時執行“插入訂單記錄”和“扣減庫存”,兩者必須同時成功或同時失敗)。
1.2 事務的ACID特性
事務必須滿足ACID特性,這是保證數據一致性的基礎:
- 原子性(Atomicity):事務中的操作要么全執行,要么全不執行(如轉賬時“扣款”和“入賬”必須同時成功);
- 一致性(Consistency):事務執行前后,數據從一個有效狀態變為另一個有效狀態(如轉賬前后總金額不變);
- 隔離性(Isolation):多個事務并發執行時,彼此不干擾(避免臟讀、不可重復讀等問題);
- 持久性(Durability):事務提交后,修改永久保存到數據庫(即使斷電也不丟失)。
1.3 Spring事務的核心優勢
傳統JDBC事務需要手動控制(conn.setAutoCommit(false)
→commit()
→rollback()
),而Spring事務的優勢在于:
- 聲明式事務:通過注解(
@Transactional
)或XML配置事務,無需編寫事務控制代碼; - AOP實現:事務邏輯與業務邏輯分離,業務代碼只關注核心邏輯;
- 靈活配置:支持自定義傳播行為、隔離級別、超時時間等;
- 整合方便:與Spring容器無縫集成,支持各種數據源和ORM框架(如MyBatis、Hibernate)。
二、Spring事務的核心配置
Spring事務的核心是@Transactional
注解,通過屬性配置事務行為,常用屬性如下:
屬性名 | 作用 | 默認值 |
---|---|---|
propagation | 事務傳播行為(如何處理嵌套事務) | Propagation.REQUIRED |
isolation | 事務隔離級別(并發控制) | Isolation.DEFAULT (數據庫默認) |
readOnly | 是否為只讀事務(優化性能) | false |
timeout | 事務超時時間(秒,超時自動回滾) | -1 (無超時) |
rollbackFor | 需要回滾的異常類型(如Exception.class ) | 僅RuntimeException 及其子類 |
noRollbackFor | 不需要回滾的異常類型 | 無 |
三、事務傳播行為(Propagation)
事務傳播行為定義了“當一個事務方法調用另一個事務方法時,事務如何傳播”,是Spring事務最核心的特性之一。
3.1 常用傳播行為詳解
3.1.1 REQUIRED
(默認值)
- 規則:如果當前存在事務,則加入該事務;如果沒有事務,則創建新事務。
- 適用場景:大多數業務方法(如訂單創建、用戶注冊)。
@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate StockService stockService;// 傳播行為:REQUIRED(默認)@Transactional(propagation = Propagation.REQUIRED)public void createOrder(Order order) {orderMapper.insert(order); // 操作1stockService.reduceStock(order.getProductId()); // 調用另一個事務方法}
}@Service
public class StockService {// 傳播行為:REQUIRED@Transactional(propagation = Propagation.REQUIRED)public void reduceStock(Long productId) {// 操作2:扣減庫存}
}
執行邏輯:
createOrder
創建事務→reduceStock
加入該事務→若操作1或2失敗,整個事務回滾(符合原子性)。
3.1.2 SUPPORTS
- 規則:如果當前存在事務,則加入該事務;如果沒有事務,則以非事務方式執行。
- 適用場景:可選事務的方法(如查詢操作,可在事務中執行,也可單獨執行)。
@Service
public class UserService {// 傳播行為:SUPPORTS@Transactional(propagation = Propagation.SUPPORTS)public User getUserById(Long id) {// 查詢用戶(非核心操作,有無事務均可)}
}
3.1.3 REQUIRES_NEW
- 規則:無論當前是否存在事務,都創建新事務(原事務暫停,新事務獨立執行)。
- 適用場景:需要獨立事務的操作(如日志記錄,即使主事務失敗也需提交)。
@Service
public class OrderService {@Autowiredprivate LogService logService;@Transactionalpublic void createOrder(Order order) {// 主事務操作:創建訂單logService.recordLog("創建訂單:" + order.getId()); // 調用獨立事務方法}
}@Service
public class LogService {// 傳播行為:REQUIRES_NEW(獨立事務)@Transactional(propagation = Propagation.REQUIRES_NEW)public void recordLog(String content) {// 記錄日志(即使主事務回滾,此操作仍會提交)}
}
執行邏輯:
主事務執行→recordLog
創建新事務→日志記錄成功提交→主事務若失敗,僅主事務回滾,日志不會回滾。
3.1.4 NEVER
- 規則:如果當前存在事務,則拋出異常;如果沒有事務,則以非事務方式執行。
- 適用場景:絕對不能在事務中執行的方法(如某些特殊查詢)。
3.1.5 MANDATORY
- 規則:如果當前存在事務,則加入該事務;如果沒有事務,則拋出異常。
- 適用場景:必須在事務中執行的方法(如核心業務操作)。
3.2 傳播行為選擇原則
- 核心業務方法(如訂單、支付):
REQUIRED
; - 查詢方法:
SUPPORTS
; - 獨立日志、審計操作:
REQUIRES_NEW
; - 必須在事務中執行的方法:
MANDATORY
。
四、事務隔離級別(Isolation)
事務隔離級別定義了“多個事務并發執行時的隔離程度”,用于解決并發問題(臟讀、不可重復讀、幻讀)。
4.1 并發事務的三大問題
問題 | 說明 | 示例 |
---|---|---|
臟讀 | 讀取到另一個未提交事務的修改 | 事務A修改數據→事務B讀取→事務A回滾→事務B讀取到無效數據 |
不可重復讀 | 同一事務中多次讀取數據不一致 | 事務A讀取數據→事務B修改并提交→事務A再次讀取,數據不同 |
幻讀 | 同一事務中多次查詢,結果集數量不一致 | 事務A查詢所有訂單→事務B新增訂單并提交→事務A再次查詢,多了一條記錄 |
4.2 隔離級別詳解
Spring支持5種隔離級別,對應數據庫的隔離級別:
隔離級別 | 解決問題 | 并發性能 | 適用場景 |
---|---|---|---|
DEFAULT (默認) | 數據庫默認隔離級別(如MySQL默認REPEATABLE_READ ) | 中等 | 大多數場景(推薦) |
READ_UNCOMMITTED | 無(允許臟讀、不可重復讀、幻讀) | 最高 | 極少使用(對一致性要求極低) |
READ_COMMITTED | 解決臟讀 | 較高 | 對一致性有基本要求(如Oracle默認) |
REPEATABLE_READ | 解決臟讀、不可重復讀 | 中等 | MySQL默認,大多數業務場景 |
SERIALIZABLE | 解決所有問題(串行執行) | 最低 | 對一致性要求極高(如金融交易) |
4.3 配置隔離級別
@Service
public class OrderService {// 設置隔離級別為REPEATABLE_READ@Transactional(isolation = Isolation.REPEATABLE_READ)public void createOrder(Order order) {// 業務邏輯}
}
注意:隔離級別受數據庫支持限制(如MySQL支持所有級別,SQL Server不支持READ_UNCOMMITTED
),實際以數據庫為準。
五、聲明式事務實戰
以“訂單創建”為例,演示事務的完整使用(包含傳播行為、異常回滾配置)。
5.1 環境準備
添加Spring事務依賴(已包含在spring-context
中),并配置數據源和事務管理器:
@Configuration
@MapperScan("com.example.mapper")
public class SpringConfig {// 數據源配置(省略,需配置正確的JDBC連接)@Beanpublic DataSource dataSource() { ... }// 事務管理器(核心)@Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}
}
5.2 業務實現
5.2.1 Mapper接口(MyBatis)
public interface OrderMapper {void insert(Order order);
}public interface StockMapper {void reduceStock(@Param("productId") Long productId, @Param("quantity") Integer quantity);
}
5.2.2 Service實現
@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate StockService stockService;/*** 創建訂單(核心業務)* 傳播行為:REQUIRED(默認)* 回滾規則:所有Exception都回滾(默認僅RuntimeException回滾,此處擴展)*/@Transactional(rollbackFor = Exception.class)public void createOrder(Order order) throws Exception {// 1. 創建訂單orderMapper.insert(order);// 2. 扣減庫存(調用另一個事務方法)stockService.reduceStock(order.getProductId(), order.getQuantity());// 3. 模擬異常(測試回滾)if (order.getTotalAmount() < 0) {throw new Exception("訂單金額不能為負"); // 觸發回滾}}
}@Service
public class StockService {@Autowiredprivate StockMapper stockMapper;// 傳播行為:REQUIRED(加入訂單事務)@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void reduceStock(Long productId, Integer quantity) {stockMapper.reduceStock(productId, quantity);// 若庫存不足,拋出異常(觸發回滾)if (/* 庫存不足 */) {throw new RuntimeException("庫存不足");}}
}
5.3 測試與驗證
5.3.1 正常流程(無異常)
@Test
public void testCreateOrderSuccess() {Order order = new Order();order.setProductId(1L);order.setQuantity(2);order.setTotalAmount(100.0);orderService.createOrder(order);// 驗證:訂單表新增記錄,庫存表數量減少(事務提交成功)
}
5.3.2 異常流程(觸發回滾)
@Test
public void testCreateOrderRollback() {Order order = new Order();order.setProductId(1L);order.setQuantity(2);order.setTotalAmount(-100.0); // 觸發異常try {orderService.createOrder(order);} catch (Exception e) {// 驗證:訂單表無新增記錄,庫存表數量未變(事務回滾成功)}
}
六、常見問題與避坑指南
6.1 事務不回滾(@Transactional失效)
6.1.1 異常類型不匹配
問題:方法拋出CheckedException
(如Exception
),但rollbackFor
未配置,導致事務不回滾。
原因:@Transactional
默認只對RuntimeException
及其子類回滾,對CheckedException
不回滾。
解決方案:
通過rollbackFor
指定需要回滾的異常:
// 對所有Exception回滾
@Transactional(rollbackFor = Exception.class)
6.1.2 方法非public
問題:非public方法(如private
、protected
)的@Transactional
無效。
原因:Spring AOP默認只對public方法增強(事務基于AOP實現)。
解決方案:
確保事務方法為public
。
6.1.3 自調用導致事務失效
問題:同一類中方法調用(自調用)時,事務不生效。
@Service
public class OrderService {public void methodA() {methodB(); // 自調用,事務不生效}@Transactionalpublic void methodB() { ... }
}
原因:Spring事務基于代理對象,自調用是目標對象內部調用,未經過代理。
解決方案:
- 注入自身代理對象調用:
@Service
public class OrderService {@Autowiredprivate OrderService orderService; // 注入自身代理public void methodA() {orderService.methodB(); // 通過代理調用,事務生效}@Transactionalpublic void methodB() { ... }
}
- 開啟暴露代理(
@EnableAspectJAutoProxy(exposeProxy = true)
),通過AopContext
獲取代理:
public void methodA() {((OrderService) AopContext.currentProxy()).methodB();
}
6.2 事務超時(Timeout)
問題:事務執行時間過長,占用數據庫連接,導致連接池耗盡。
解決方案:
設置合理的超時時間(秒),超時自動回滾并釋放連接:
// 超時時間30秒
@Transactional(timeout = 30)
public void createOrder(Order order) { ... }
6.3 只讀事務(readOnly)
對查詢方法設置readOnly = true
,提示數據庫優化事務(如避免寫操作、啟用緩存):
// 只讀事務(查詢方法)
@Transactional(readOnly = true)
public List<Order> getOrders() { ... }
注意:只讀事務中執行insert
/update
會拋出異常(取決于數據庫)。
總結:事務使用的最佳實踐
Spring事務簡化了事務管理,但需遵循最佳實踐避免常見問題:
- 核心配置:
- 對所有業務方法顯式指定
rollbackFor = Exception.class
(避免異常不回滾);- 核心業務用
REQUIRED
傳播行為,獨立操作(如日志)用REQUIRES_NEW
;- 查詢方法設置
readOnly = true
優化性能。
- 避坑要點:
- 事務方法必須為
public
;- 避免自調用(或通過代理對象調用);
- 合理設置超時時間,避免長事務。
- 性能優化:
- 事務范圍盡可能小(僅包含必要操作,避免在事務中調用外部接口、等待用戶輸入);
- 讀多寫少場景用較高隔離級別(如
READ_COMMITTED
),減少鎖競爭。事務是保證數據一致性的最后一道防線,正確使用能避免數據錯亂(如重復下單、庫存超賣)等嚴重問題。建議在開發中結合日志(如打印事務開始/提交/回滾日志)和測試(模擬異常驗證回滾)確保事務生效。
若這篇內容幫到你,動動手指支持下!關注不迷路,干貨持續輸出!
ヾ(′? ˋ)ノヾ(′? ˋ)ノヾ(′? ˋ)ノヾ(′? ˋ)ノヾ(′? ˋ)ノ