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

