Java?Spring事務(wù)管理超詳細指南
一、 引言:為什么我們需要事務(wù)?
1.1 一個經(jīng)典的轉(zhuǎn)賬場景
想象一下,你正在使用手機銀行給朋友轉(zhuǎn)賬 100 塊錢。這個動作在代碼層面通常分為兩步:
- 你的賬戶扣款 100 元 (
UPDATE account SET balance = balance - 100 WHERE id = A) - 朋友的賬戶增加 100 元 (
UPDATE account SET balance = balance + 100 WHERE id = B)
如果發(fā)生了意外會怎樣?
- 假如第一步執(zhí)行成功了,你的錢扣了。
- 緊接著,程序報錯了(比如斷網(wǎng)了、數(shù)據(jù)庫崩了、或者代碼拋異常了)。
- 結(jié)果:你的錢沒了,朋友也沒收到錢!?? 這就是嚴重的數(shù)據(jù)不一致問題。
1.2 事務(wù)的作用
為了解決這個問題,數(shù)據(jù)庫引入了 事務(wù)(Transaction) 的概念。
事務(wù)就是把一組數(shù)據(jù)庫操作看作一個整體。這個整體內(nèi)的操作,要么全部成功,要么全部失敗(回滾),不允許出現(xiàn) “做一半” 的情況。
二、 事務(wù)的四大特性(ACID)
這是面試必考題!理解這四個詞,就理解了事務(wù)的靈魂。
| 特性 | 英文 | 解釋 | 通俗理解 |
|---|---|---|---|
| 原子性 | Atomicity | 事務(wù)是不可分割的最小單位,要么全做,要么全不做。 | 要么大家都成功,要么大家一起"死"(回滾),不能有幸存者。 |
| 一致性 | Consistency | 事務(wù)執(zhí)行前后,數(shù)據(jù)必須保持合規(guī)的邏輯狀態(tài)。 | 轉(zhuǎn)賬前 A+B=200,轉(zhuǎn)賬后 A+B 還是 200,錢不會憑空消失或變多。 |
| 隔離性 | Isolation | 多個事務(wù)并發(fā)執(zhí)行時,互不干擾。 | 我在操作這條數(shù)據(jù)時,你別來搗亂(具體看隔離級別)。 |
| 持久性 | Durability | 事務(wù)一旦提交,修改就是永久的,即使系統(tǒng)崩潰也不丟失。 | 落子無悔,寫進硬盤了,斷電也沒事。 |
三、 Spring 中的事務(wù)管理
在 Java 開發(fā)中,我們幾乎都在使用 Spring 框架來管理事務(wù)。Spring 提供了兩種方式:
- 編程式事務(wù):在代碼里手動寫
commit(),rollback()(代碼侵入性太強,現(xiàn)在很少用了)。 - 聲明式事務(wù):使用注解
@Transactional,把繁瑣的事務(wù)邏輯交給 Spring 代理處理(推薦,最常用)。
3.1 快速入門代碼示例
下面是一個典型的 Service 層代碼,演示了如何使用 @Transactional。
package com.example.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.mapper.AccountMapper;
@Service
public class AccountService {
@Autowired
private AccountMapper accountMapper;
/**
* 轉(zhuǎn)賬方法
*
* @Transactional 注解說明:
* 1. 這是一個事務(wù)方法。
* 2. Spring 會自動在方法開始前開啟事務(wù)(Begin)。
* 3. 如果方法正常執(zhí)行結(jié)束,Spring 自動提交事務(wù)(Commit)。
* 4. 如果方法拋出了 RuntimeException (運行時異常),Spring 自動回滾事務(wù)(Rollback)。
*/
@Transactional(rollbackFor = Exception.class) // 建議:顯式指定遇到任何異常都回滾
public void transfer(Long fromId, Long toId, Double amount) {
// 1. 扣錢 (可能發(fā)生異常的地方)
accountMapper.decreaseBalance(fromId, amount);
System.out.println("用戶 " + fromId + " 扣款成功");
// 模擬一個意外異常:例如除以零,或者空指針
// int i = 1 / 0;
// 如果上面這行解開注釋,整個事務(wù)會回滾,第一步扣的錢會加回去。
// 2. 加錢
accountMapper.increaseBalance(toId, amount);
System.out.println("用戶 " + toId + " 入賬成功");
// 方法結(jié)束 -> 提交事務(wù)
}
}
四、 核心難點:事務(wù)的傳播行為 (Propagation)
“傳播行為” 是 Spring 特有的概念,解決的是 Service 方法互相調(diào)用 時,事務(wù)該怎么算的問題。
比如:方法 A 調(diào)用了 方法 B,由于 A 和 B 上都有 @Transactional 注解,那 B 是加入 A 的事務(wù)?還是自己新開一個?
Spring 定義了 7 種傳播行為,最常用的有下面 3 種:
1. REQUIRED (默認值)
?? 口語解釋:“有就加入,沒有就新建。”
- 場景:如果不指定,默認就是這個。
- 行為:
- 如果 A 已經(jīng)開啟了事務(wù),B 就加入 A 的事務(wù)(它倆是同一條船上的螞蚱,要么一起成功,要么一起回滾)。
- 如果 A 沒有事務(wù),B 就自己開啟一個新的事務(wù)。
2. REQUIRES_NEW
?? 口語解釋:“不管你有沒有,我都自己玩。”
- 場景:記錄日志操作。不管業(yè)務(wù)邏輯成功還是失敗,日志都必須記錄下來,不能因為業(yè)務(wù)回滾了日志也就沒了。
- 行為:
- B 方法會掛起 A 的事務(wù),自己開啟一個全新的事務(wù)。
- B 的成功失敗,不影響 A;A 的回滾,也不影響 B(前提是 B 已經(jīng)提交了)。
- 代碼示例:
@Transactional(propagation = Propagation.REQUIRES_NEW) public void logOperation() { // 這里的操作在一個完全獨立的事務(wù)中 logMapper.insertLog("操作發(fā)生"); }
3. NESTED (嵌套事務(wù))
?? 口語解釋:“你是父,我是子。你掛我也掛,我掛你不一定掛。”
- 行為:
- 基于數(shù)據(jù)庫的 Savepoint(保存點)技術(shù)。
- B 是 A 的一個子事務(wù)。如果 A 回滾,B 一定回滾。
- 但是如果 B 異?;貪L了,A 可以選擇捕獲異常,繼續(xù)執(zhí)行其他邏輯,不一定要回滾。
五、 核心難點:事務(wù)的隔離級別 (Isolation)
當(dāng)很多用戶同時操作數(shù)據(jù)庫時(高并發(fā)),會產(chǎn)生一些奇怪的現(xiàn)象。通過設(shè)置隔離級別來權(quán)衡數(shù)據(jù)的準確性和性能。
5.1 并發(fā)可能導(dǎo)致的問題
- 臟讀 (Dirty Read):讀到了別人還沒提交的數(shù)據(jù)。(最嚴重,絕對不允許)
- 例子:A 沒提交轉(zhuǎn)賬,B 讀到了錢多了,結(jié)果 A 回滾了,B 讀到的是假數(shù)據(jù)。
- 不可重復(fù)讀 (Non-repeatable Read):在一個事務(wù)里,兩次讀取同一行數(shù)據(jù),結(jié)果不一樣(因為中間被別人改了)。
- 例子:我看庫存是 1,準備買,中間被別人買走了,我再看庫存變成 0 了。
- 幻讀 (Phantom Read):在一個事務(wù)里,兩次查詢通過同樣的條件,結(jié)果條數(shù)不一樣(因為中間別人插入/刪除了數(shù)據(jù))。
5.2 SQL 標(biāo)準的四個隔離級別
| 級別 | 名稱 | 解決的問題 | 性能 |
|---|---|---|---|
| READ_UNCOMMITTED | 讀未提交 | 啥也沒解決,可能臟讀 | 最高(極不安全) |
| READ_COMMITTED | 讀已提交 | 解決了臟讀 (Oracle/SQLServer 默認) | 較好 |
| REPEATABLE_READ | 可重復(fù)讀 | 解決了臟讀、不可重復(fù)讀 (MySQL 默認) | 一般 |
| SERIALIZABLE | 串行化 | 解決了所有問題 (完全排隊執(zhí)行) | 最差(像單線程) |
?? Spring 配置方式:
@Transactional(isolation = Isolation.REPEATABLE_READ)
六、 常見坑點:為什么我的 @Transactional 失效了?
這是新手最容易遇到的問題,代碼寫了注解,但異常拋出時數(shù)據(jù)居然沒有回滾!常見原因如下:
- 方法不是 public 的:Spring 默認只代理 public 方法。
- 同類內(nèi)部調(diào)用:
- 錯誤示范:方法 A 調(diào) 方法 B,A 沒有注解,B 有注解。在 Controller 調(diào) A 時,B 的事務(wù)不會生效。
- 原因:Spring 事務(wù)是基于 AOP 代理 的,同類內(nèi)部調(diào)用
this.methodB()是直接調(diào)用原始對象的方法,繞過了 Spring 的代理對象,所以事務(wù)沒開啟。
- 異常被你自己 catch 吃了:
- 錯誤示范:
@Transactional public void method() { try { // 業(yè)務(wù)代碼 } catch (Exception e) { e.printStackTrace(); // ? 錯誤!這里把異常捕獲了,Spring 以為代碼執(zhí)行正常,就會提交事務(wù)! } } - 修正:在 catch 塊里
throw new RuntimeException(e)或者手動回滾TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();。
- 錯誤示范:
- 數(shù)據(jù)庫引擎不支持:例如 MySQL 的 MyISAM 引擎是不支持事務(wù)的,必須用 InnoDB。
七、 高頻面試題 QA
Q1:Spring 事務(wù)默認回滾什么異常?
A:默認只回滾
RuntimeException(運行時異常)和Error。對于Exception(受檢異常,如IOException),默認是不回滾的。
追問:怎么讓受檢異常也回滾?
A:配置@Transactional(rollbackFor = Exception.class)。
Q2:REQUIRED 和 REQUIRES_NEW 的區(qū)別?
A:
- REQUIRED:如果當(dāng)前有事務(wù),就加入;如果沒有,就新建。多個方法共用一個物理事務(wù),一起提交或回滾。
- REQUIRES_NEW:無論當(dāng)前有沒有事務(wù),都掛起當(dāng)前事務(wù),自己開啟一個新事務(wù)。兩個事務(wù)互不影響,是隔離的。
Q3:什么是 Spring 的事務(wù)失效?舉個例子。
A:最經(jīng)典的就是自調(diào)用問題。在同一個 Service 類中,非事務(wù)方法 A 調(diào)用事務(wù)方法 B,導(dǎo)致 B 的事務(wù)失效。因為 Spring AOP 生成代理對象時,只有通過代理對象調(diào)用方法才能攔截事務(wù),內(nèi)部
this調(diào)用直接走了目標(biāo)對象。
Q4:ACID 是靠什么保證的?(進階)
A:
- A (原子性):靠
undo log(回滾日志) 保證,失敗了可以回滾。- C (一致性):是最終目的,靠代碼邏輯和 AID 共同保證。
- I (隔離性):靠
MVCC(多版本并發(fā)控制) 和鎖機制保證。- D (持久性):靠
redo log(重做日志) 保證,斷電也能恢復(fù)。
八、 總結(jié)
事務(wù)管理是后端開發(fā)的"安全帶"。雖然 Spring Boot 讓我們使用 @Transactional 一個注解就能搞定事務(wù),但作為開發(fā)者,我們必須深入理解其背后的傳播機制和隔離級別,才能在復(fù)雜的業(yè)務(wù)場景下寫出健壯的代碼。
到此這篇關(guān)于Java Spring事務(wù)管理的文章就介紹到這了,更多相關(guān)Java Spring事務(wù)管理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實現(xiàn)Excel批量導(dǎo)入數(shù)據(jù)
這篇文章主要為大家詳細介紹了Java實現(xiàn)Excel批量導(dǎo)入數(shù)據(jù),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-08-08
SpringBoot整合DeepSeek技術(shù)指南(實際應(yīng)用場景)
這篇文章主要介紹了SpringBoot整合DeepSeek技術(shù)指南,本文通過實際應(yīng)用場景模擬給大家介紹的非常詳細,感興趣的朋友一起看看吧2025-04-04
mybatis多層嵌套resultMap及返回自定義參數(shù)詳解
這篇文章主要介紹了mybatis多層嵌套resultMap及返回自定義參數(shù)詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12
SpringBoot淺析安全管理之Spring Security配置
安全管理是軟件系統(tǒng)必不可少的的功能。根據(jù)經(jīng)典的“墨菲定律”——凡是可能,總會發(fā)生。如果系統(tǒng)存在安全隱患,最終必然會出現(xiàn)問題,這篇文章主要介紹了SpringBoot安全管理Spring Security基本配置2022-08-08
Java服務(wù)端服務(wù)監(jiān)控:Prometheus與Spring Boot Actuator的集成方式
本文介紹了如何將Prometheus與SpringBootActuator集成,實現(xiàn)對Java服務(wù)端應(yīng)用的監(jiān)控,通過集成,可以利用Prometheus的強大監(jiān)控能力,及時發(fā)現(xiàn)和解決性能問題2024-12-12
SpringBoot中配置雙數(shù)據(jù)源的實現(xiàn)示例
在許多應(yīng)用程序中,可能會遇到需要連接多個數(shù)據(jù)庫的情況,本文主要介紹了SpringBoot中配置雙數(shù)據(jù)源的實現(xiàn)示例,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-08-08

