Spring聲明式事務(wù)管理從原理到實(shí)戰(zhàn)示例
?? 一、什么是聲明式事務(wù)管理?
? 簡單說:
你不用在代碼里手動(dòng)寫 beginTransaction()、commit()、rollback(),而是通過配置或注解來告訴 Spring:“這個(gè)方法需要事務(wù)”,Spring 自動(dòng)幫你管理事務(wù)的開始、提交或回滾。
?? 對比:編程式 vs 聲明式
| 類型 | 特點(diǎn) | 代碼侵入性 |
|---|---|---|
| 編程式事務(wù) | 手動(dòng)控制事務(wù)(如使用 TransactionTemplate) | 高(代碼里到處是事務(wù)邏輯) |
| 聲明式事務(wù) | 用配置或注解聲明事務(wù)行為 | 低(業(yè)務(wù)代碼干凈) |
?? Spring 推薦使用 聲明式事務(wù),因?yàn)樗?ldquo;輕量級(jí)容器”的理念 —— 業(yè)務(wù)代碼不依賴事務(wù)框架。
?? 二、聲明式事務(wù)是如何實(shí)現(xiàn)的?(核心原理)
Spring 的聲明式事務(wù)是基于 AOP(面向切面編程) 實(shí)現(xiàn)的。
?? 工作流程如下:
- 你有一個(gè)服務(wù)類,比如
DefaultFooService。 - 你在配置中聲明了哪些方法需要事務(wù)(通過
<tx:advice>或@Transactional)。 - Spring 在啟動(dòng)時(shí),會(huì)為這個(gè)類創(chuàng)建一個(gè) 代理對象(Proxy)。
- 當(dāng)你調(diào)用
fooService.insertFoo(...)時(shí),實(shí)際上是調(diào)用了代理對象的方法。 - 代理對象在方法執(zhí)行前開啟事務(wù),執(zhí)行后根據(jù)結(jié)果決定提交或回滾。
你調(diào)用: fooService.insertFoo()
↓
實(shí)際執(zhí)行: Proxy → 開啟事務(wù) → 調(diào)用真實(shí)方法 → 成功則提交,異常則回滾
??? 關(guān)鍵組件:
TransactionInterceptor:事務(wù)攔截器,負(fù)責(zé)在方法前后插入事務(wù)邏輯。TransactionManager:事務(wù)管理器,真正執(zhí)行開啟、提交、回滾操作(如DataSourceTransactionManager)。- AOP 代理:JDK 動(dòng)態(tài)代理 或 CGLIB,用于織入事務(wù)邏輯。
?? 三、如何配置聲明式事務(wù)?(XML 配置方式)
文檔中的例子使用 XML 配置,雖然現(xiàn)在更多用注解,但理解 XML 有助于理解底層機(jī)制。
示例配置解析:
<!-- 定義數(shù)據(jù)源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> ... </bean>
<!-- 定義事務(wù)管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 定義事務(wù)通知(規(guī)則) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/> <!-- 所有g(shù)et開頭的方法:只讀事務(wù) -->
<tx:method name="*" /> <!-- 其他方法:默認(rèn)事務(wù)(讀寫) -->
</tx:attributes>
</tx:advice>
<!-- 將事務(wù)通知應(yīng)用到指定的切點(diǎn) -->
<aop:config>
<!-- 切點(diǎn):匹配 FooService 接口的所有方法 -->
<aop:pointcut id="fooServiceOperation"
expression="execution(* x.y.service.FooService.*(..))"/>
<!-- 綁定通知和切點(diǎn) -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>?? 關(guān)鍵點(diǎn):
tx:method name="get*":所有以get開頭的方法使用 只讀事務(wù)(性能優(yōu)化)。tx:method name="*":其他方法使用默認(rèn)事務(wù)(讀寫)。aop:pointcut使用 AspectJ 表達(dá)式 匹配方法。
?? 四、事務(wù)回滾機(jī)制(Rollback Rules)
這是 Spring 事務(wù)的一大亮點(diǎn):你可以精確控制哪些異常觸發(fā)回滾。
默認(rèn)規(guī)則:
- ? 運(yùn)行時(shí)異常(RuntimeException):自動(dòng)回滾(如
NullPointerException、IllegalArgumentException) - ? 檢查型異常(Checked Exception):不回滾(如
IOException、自定義的MyException)
?? 這與 EJB CMT 不同!EJB 中檢查型異常也不回滾,但 Spring 允許你自定義。
自定義回滾規(guī)則:
1. 某個(gè)檢查型異常也要回滾:
<tx:method name="updateStock" rollback-for="NoProductInStockException"/>
2. 某個(gè)運(yùn)行時(shí)異常不要回滾:
<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
3. 多規(guī)則優(yōu)先級(jí):
<tx:method name="*"
rollback-for="Throwable"
no-rollback-for="InstrumentNotFoundException"/>
? 所有異常都回滾,除了
InstrumentNotFoundException。
?? 五、響應(yīng)式事務(wù)(Reactive Transaction Management)
Spring 5+ 支持響應(yīng)式編程(如 WebFlux),事務(wù)也支持響應(yīng)式。
關(guān)鍵區(qū)別:
| 特性 | 傳統(tǒng)(Imperative) | 響應(yīng)式(Reactive) |
|---|---|---|
| 返回類型 | Foo, void | Mono<Foo>, Flux<Foo> |
| 事務(wù)管理器 | PlatformTransactionManager | ReactiveTransactionManager |
| 事務(wù)傳播 | 基于 ThreadLocal | 基于 Reactor Context |
| 事務(wù)觸發(fā) | 方法調(diào)用立即開始 | 返回的 Publisher 被訂閱時(shí)才開始 |
?? 響應(yīng)式事務(wù)是“惰性的”:調(diào)用方法不立即開啟事務(wù),而是等到
.subscribe()時(shí)才開始。
??? 六、不同 Bean 使用不同事務(wù)配置
你可以為不同的服務(wù)類設(shè)置不同的事務(wù)規(guī)則。
示例:
<aop:pointcut id="defaultServiceOperation"
expression="execution(* x.y.service.*Service.*(..))"/>
<aop:pointcut id="noTxServiceOperation"
expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/>
<aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>
<aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>*Service類:使用默認(rèn)事務(wù)(可讀寫)DefaultDdlManager類:使用propagation="NEVER"(禁止在事務(wù)中運(yùn)行)
?? 這很實(shí)用,比如 DDL 操作(建表、刪表)通常不應(yīng)在事務(wù)中運(yùn)行。
?? 七、<tx:method>的所有配置項(xiàng)總結(jié)
| 屬性 | 是否必需 | 默認(rèn)值 | 說明 |
|---|---|---|---|
name | ? 是 | - | 方法名(支持 * 通配符) |
propagation | 否 | REQUIRED | 傳播行為(如 REQUIRED, REQUIRES_NEW, NEVER 等) |
isolation | 否 | DEFAULT | 隔離級(jí)別(如 READ_COMMITTED) |
timeout | 否 | -1(無超時(shí)) | 超時(shí)時(shí)間(秒) |
read-only | 否 | false | 是否只讀事務(wù) |
rollback-for | 否 | - | 哪些異常觸發(fā)回滾(可寫多個(gè),逗號(hào)分隔) |
no-rollback-for | 否 | - | 哪些異常不觸發(fā)回滾 |
?? 八、重要注意事項(xiàng)
- 代理機(jī)制限制:
- 只有 外部調(diào)用 代理對象的方法才會(huì)觸發(fā)事務(wù)。
- 如果你在同一個(gè)類中調(diào)用
this.method(),事務(wù)不會(huì)生效(因?yàn)闆]走代理)。
- 事務(wù)不會(huì)跨遠(yuǎn)程調(diào)用:
- Spring 不支持像 EJB 那樣將事務(wù)上下文傳播到遠(yuǎn)程服務(wù)。
- 如果你需要分布式事務(wù),考慮使用 Seata 或 Spring Cloud Alibaba 等方案。
- 異常必須拋出:
- 如果你在方法內(nèi)部
catch了異常但沒有重新拋出,事務(wù)不會(huì)回滾。 - 正確做法:
catch后throw或調(diào)用setRollbackOnly()。
- 如果你在方法內(nèi)部
@Transactional注解更常用:- 現(xiàn)代 Spring 開發(fā)通常使用
@Transactional注解代替 XML。
- 現(xiàn)代 Spring 開發(fā)通常使用
- 例如:
@Service
public class FooService {
@Transactional(readOnly = true)
public Foo getFoo(String name) { ... }
@Transactional(rollbackFor = MyException.class)
public void updateFoo(Foo foo) { ... }
}需要啟用:@EnableTransactionManagement
? 總結(jié):如何理解這段內(nèi)容?
| 問題 | 回答 |
|---|---|
| 聲明式事務(wù)是什么? | 用配置或注解聲明事務(wù),無需手動(dòng)控制。 |
| 怎么實(shí)現(xiàn)的? | 基于 AOP,Spring 創(chuàng)建代理對象,在方法前后自動(dòng)加事務(wù)邏輯。 |
| 怎么配置? | 用 <tx:advice> 定義規(guī)則,用 <aop:config> 應(yīng)用到目標(biāo)方法。 |
| 異常怎么回滾? | 默認(rèn)運(yùn)行時(shí)異常回滾,可通過 rollback-for 自定義。 |
| 不同類不同配置? | 可以,定義多個(gè) <tx:advice> 和 <aop:advisor>。 |
| 響應(yīng)式支持嗎? | 支持,基于 ReactiveTransactionManager 和 Reactor 上下文。 |
?? 建議學(xué)習(xí)路徑
- 先理解 AOP 基本概念(代理、切點(diǎn)、通知)
- 動(dòng)手寫一個(gè) XML 配置的聲明式事務(wù)例子
- 改造成
@Transactional注解方式 - 嘗試 自定義回滾規(guī)則
- 了解 事務(wù)傳播行為(如
REQUIRED,REQUIRES_NEW) - (進(jìn)階)學(xué)習(xí) 響應(yīng)式事務(wù) 和 分布式事務(wù)
如果你希望,我可以:
- 把這個(gè)內(nèi)容轉(zhuǎn)成中文思維導(dǎo)圖
- 寫一個(gè)完整的可運(yùn)行代碼示例(Java + Spring + 注解)
- 解釋
@Transactional的各種傳播行為
歡迎繼續(xù)提問!
到此這篇關(guān)于Spring聲明式事務(wù)管理從原理到實(shí)戰(zhàn)示例的文章就介紹到這了,更多相關(guān)Spring聲明式事務(wù)管理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決SpringBoot整合ElasticSearch遇到的連接問題
這篇文章主要介紹了解決SpringBoot整合ElasticSearch遇到的連接問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
Java實(shí)現(xiàn)普通類注入service對象
這篇文章主要介紹了Java實(shí)現(xiàn)普通類注入service對象,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
已解決:No ''Access-Control-Allow-Origin''跨域問題
這篇文章主要介紹了已解決:No 'Access-Control-Allow-Origin' 跨域,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06
解析Java中的隊(duì)列和用LinkedList集合模擬隊(duì)列的方法
這篇文章主要介紹了解析Java中的隊(duì)列和用LinkedList集合模擬隊(duì)列的方法,相關(guān)算法的實(shí)現(xiàn)也是ACM中的常見題目,需要的朋友可以參考下2015-08-08
MyBatis Mapper XML中比較操作符轉(zhuǎn)義問題解決
在使用MyBatis編寫Mapper XML時(shí),有時(shí)會(huì)遇到比較操作符需要進(jìn)行轉(zhuǎn)義的情況,本文主要介紹了MyBatis Mapper XML中比較操作符轉(zhuǎn)義問題解決,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01
java 遠(yuǎn)程文件url如何轉(zhuǎn)為輸入流
這篇文章主要介紹了java 遠(yuǎn)程文件url如何轉(zhuǎn)為輸入流方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
java 字符串內(nèi)存分配的分析與總結(jié)(推薦)
下面小編就為大家?guī)硪黄猨ava 字符串內(nèi)存分配的分析與總結(jié)(推薦)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-08-08

