如何合理使用Spring的事務(wù)方式
Spring Framework 提供了全面的事務(wù)管理功能,以確保在應(yīng)用程序中操作的原子性、一致性、隔離性和持久性(即 ACID 特性)。
事務(wù)管理是重要的,尤其是在涉及到數(shù)據(jù)庫操作時(shí),以確保數(shù)據(jù)的完整性和一致性。
1、介紹
1.1、底層構(gòu)造
Spring事務(wù)管理高層抽象主要包括3個(gè)接口,Spring的事務(wù)主要是由他們共同完成的:
PlatformTransactionManager:事務(wù)管理器—主要用于平臺(tái)相關(guān)事務(wù)的管理TransactionDefinition:事務(wù)定義信息(隔離、傳播、超時(shí)、只讀)—通過配置如何進(jìn)行事務(wù)管理。TransactionStatus:事務(wù)具體運(yùn)行狀態(tài)—事務(wù)管理過程中,每個(gè)時(shí)間點(diǎn)事務(wù)的狀態(tài)信息。
1.1.事務(wù)管理器

PlatformTransactionManager該接口提供三個(gè)方法:
commit:提交事務(wù)rollback:回滾事務(wù)getTransaction:獲取事務(wù)狀態(tài)
1.2.事務(wù)定義信息

TransactionDefinition該接口主要提供的方法:
getIsolationLevel:隔離級(jí)別獲取getPropagationBehavior:傳播行為獲取getTimeout:獲取超時(shí)時(shí)間isReadOnly是否只讀(保存、更新、刪除—對(duì)數(shù)據(jù)進(jìn)行操作-變成可讀寫的,查詢-設(shè)置這個(gè)屬性為true,只能讀不能寫)
1.3.事務(wù)狀態(tài)
TransactionStatus代表獲取事務(wù)的狀態(tài)。
try {
操作
} catch (){
rollback
} finally {
commit
}1.4.聯(lián)系
用戶管理事務(wù),先配置事務(wù)管理方案TransactionDefinition、管理事務(wù)通過TransactionManager完成,TransactionManager根據(jù) TransactionDefinition進(jìn)行事務(wù)管理,
在事務(wù)運(yùn)行過程中,每個(gè)時(shí)間點(diǎn)都可以通過獲取TransactionStatus了解事務(wù)運(yùn)行狀態(tài)!
1.2、特點(diǎn)
定義
- 事務(wù)是一個(gè)操作序列,這些操作要么全部成功,要么全部失敗。無法將部分操作視為成功,其他部分則視為失敗。因此,事務(wù)是一種確保數(shù)據(jù)一致性和完整性的重要手段。
ACID 特性
- 原子性(Atomicity): 事務(wù)內(nèi)的所有操作要么全部成功,要么全部失敗。
- 一致性(Consistency): 事務(wù)必須使數(shù)據(jù)庫從一個(gè)一致的狀態(tài)轉(zhuǎn)變到另一個(gè)一致的狀態(tài)。
- 隔離性(Isolation): 一個(gè)事務(wù)的執(zhí)行不能被其他事務(wù)干擾,確保并發(fā)執(zhí)行下的每個(gè)事務(wù)都是在其自己的上下文中執(zhí)行。
- 持久性(Durability): 事務(wù)一旦提交,其結(jié)果便永久保存,即使系統(tǒng)故障也不會(huì)丟失。
1.3、原理
常見面試題:事務(wù)的原理
Spring 事務(wù)的底層實(shí)現(xiàn)主要使用的技術(shù):AOP(動(dòng)態(tài)代理) + ThreadLocal + try/catch。
1.動(dòng)態(tài)代理AOP:
基本所有要進(jìn)行邏輯增強(qiáng)的地方都會(huì)用到動(dòng)態(tài)代理,AOP 底層也是通過動(dòng)態(tài)代理實(shí)現(xiàn)。
2.事務(wù)同步管理器(TransactionSynchronizationManager)
Spring使用線程局部變量(ThreadLocal)來管理事務(wù)資源:
- ThreadLocal:主要用于線程間的資源隔離,以此實(shí)現(xiàn)不同線程可以使用不同的數(shù)據(jù)源、隔離級(jí)別等等。
public abstract class TransactionSynchronizationManager {
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<>("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<>("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<>("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<>("Actual transaction active");
}3.try/catch:
最終是執(zhí)行 commit 還是 rollback,是根據(jù)業(yè)務(wù)邏輯處理是否拋出異常來決定。
Spring 事務(wù)的核心邏輯偽代碼如下:
public void invokeWithinTransaction() {
// 1.事務(wù)資源準(zhǔn)備
try {
// 2.業(yè)務(wù)邏輯處理,也就是調(diào)用被代理的方法
} catch (Exception e) {
// 3.出現(xiàn)異常,進(jìn)行回滾并將異常拋出
} finally {
// 現(xiàn)場(chǎng)還原:還原舊的事務(wù)信息
}
// 4.正常執(zhí)行,進(jìn)行事務(wù)的提交
// 返回業(yè)務(wù)邏輯處理結(jié)果
}4.流程
@Transactional方法調(diào)用 │ ↓ TransactionInterceptor攔截 │ ↓ 獲取事務(wù)屬性(@Transactional配置) │ ↓ 根據(jù)傳播行為決定是否創(chuàng)建新事務(wù) │ ↓ TransactionSynchronizationManager綁定資源到當(dāng)前線程 │ ↓ 執(zhí)行業(yè)務(wù)方法 │ ┌─────────┴──────────┐ ↓ ↓ 方法成功 方法拋出異常 │ │ ↓ ↓ 提交事務(wù) 判斷是否需要回滾 │ │ ↓ ↓ 清理線程資源 回滾事務(wù) │ │ ↓ ↓ 返回結(jié)果 拋出異常
2. Spring 的事務(wù)管理
Spring 提供了強(qiáng)大的事務(wù)管理功能,包括編程式和聲明式事務(wù)管理。
1、聲明式事務(wù)管理
(xml和注解形式)
特點(diǎn)
- 易于使用:只需在方法上添加注解或在配置文件中定義事務(wù)規(guī)則即可啟用事務(wù)管理。
- 非侵入性:事務(wù)管理邏輯與業(yè)務(wù)邏輯分離,不會(huì)干擾業(yè)務(wù)邏輯代碼。
- 可配置性:可以靈活地配置事務(wù)的傳播行為、隔離級(jí)別、超時(shí)和回滾規(guī)則等。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Transactional // 默認(rèn)傳播行為為 REQUIRED
public void createUser(String username) {
System.out.println("User " + username + " created.");
// 調(diào)用另一個(gè)方法
addUser(username);
}
@Transactional(propagation = Propagation.REQUIRES_NEW) // 新事務(wù)
public void addUser(String username) {
System.out.println("User " + username + " added in a new transaction.");
}
@Transactional(propagation = Propagation.NESTED) // 嵌套事務(wù)
public void updateUser(String username) {
System.out.println("Updating user: " + username);
}
}
輸出:
在調(diào)用 createUser 方法之后,如果內(nèi)部調(diào)用 addUser,因?yàn)?addUser 不會(huì)使用aop代理,
這也意味著,addUser 的成功或失敗不會(huì)影響到 createUser 所在的原有事務(wù)。
2、編程式事務(wù)
通過TransactionTemplate手動(dòng)管理事務(wù)。這里不過多講解,實(shí)際使用較少。
在代碼中顯式地管理事務(wù)的開始、提交和回滾。這種方式更加靈活,但同時(shí)也增加了代碼的復(fù)雜性。
有興趣的可以參考:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private PlatformTransactionManager transactionManager;
public void createUser(User user) {
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
userRepository.save(user);
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}和JDBC相比:
- 不需要開始或是關(guān)閉connection(可能還有try-finally的語法),相對(duì)應(yīng)的,使用的是Transaction callbacks。
- 也不需要手動(dòng)的catch SQLExceptions,因?yàn)镾pring會(huì)把這個(gè)異常當(dāng)作runtime exceptions來處理。
- 當(dāng)然想要使用TransactionTemplate,需要先設(shè)置TransactionManager,并需要傳入dataSource。這些都可以配置成Spring的bean,放到context中。
3、事務(wù)傳播行為
Spring 提供了多種事務(wù)傳播行為來定義一個(gè)事務(wù)方法應(yīng)如何參與現(xiàn)有事務(wù)。常用的傳播行為包括:
對(duì)事務(wù)比較積極的三個(gè)傳播行為:
REQUIRED: 使用現(xiàn)有的事務(wù),如果沒有,則創(chuàng)建一個(gè)新的事務(wù)(默認(rèn)值)。REQUIRES_NEW: 總是創(chuàng)建一個(gè)新的事務(wù),并掛起當(dāng)前事務(wù)。MANDATORY: 必須存在一個(gè)當(dāng)前事務(wù),若沒有則拋出異常。
觀望者:
NESTED: 在當(dāng)前事務(wù)中執(zhí)行一個(gè)嵌套事務(wù)(支持保存點(diǎn))。
比較佛系的三個(gè)傳播行為
SUPPORTS: 存在則加入,不存在,則非事務(wù)運(yùn)行。PROPAGATION_NOT_SUPPORTED:不能參與。PROPAGATION_NEVER:不參與,參與事務(wù),則拋出異常。
3、事務(wù)的隔離級(jí)別
1、并發(fā)事務(wù)的問題

2、隔離級(jí)別


3、如何設(shè)置
(1) 聲明式事務(wù)(通過 @Transactional 注解)
@Transactional(isolation = Isolation.READ_COMMITTED)
public void transferMoney() {
// 業(yè)務(wù)邏輯
}(2) 編程式事務(wù)(通過 TransactionTemplate)
@Autowired
private TransactionTemplate transactionTemplate;
public void executeInTransaction() {
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
transactionTemplate.execute(status -> {
// 業(yè)務(wù)邏輯
return null;
});
}4、選擇隔離級(jí)別
優(yōu)先使用默認(rèn)值(ISOLATION_DEFAULT)
- 除非有明確需求,否則依賴數(shù)據(jù)庫默認(rèn)配置,避免框架與數(shù)據(jù)庫行為不一致。
平衡性能與一致性
- 高并發(fā)場(chǎng)景 →
READ_COMMITTED(減少鎖競(jìng)爭(zhēng))。 - 強(qiáng)一致性場(chǎng)景 →
REPEATABLE_READ或SERIALIZABLE(需測(cè)試性能影響)。
5、查看隔離級(jí)別
- 數(shù)據(jù)庫日志:執(zhí)行
SHOW VARIABLES LIKE 'transaction_isolation';(MySQL)或等效命令。 - 代碼調(diào)試:通過
TransactionSynchronizationManager.getCurrentTransactionIsolationLevel()獲取。
4、事務(wù)失效
1. 直接調(diào)用同一類中的事務(wù)方法
在同一類中直接調(diào)用被事務(wù)注解的方法會(huì)導(dǎo)致事務(wù)失效。因?yàn)?Spring 的 AOP 代理只在通過 Spring 管理的 Bean 進(jìn)行調(diào)用時(shí)才會(huì)生效。
代碼示例:
@Service
public class UserServiceTxImpl implements UserServiceTx {
@Transactional(rollbackFor = RuntimeException.class) // 默認(rèn)傳播行為為 REQUIRED
public void createUser(String username) {
System.out.println("User " + username + " created.");
// 調(diào)用另一個(gè)方法
addUser(username);
System.out.println("User " + username + " createUsed ");
}
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = RuntimeException.class) // 新事務(wù)
public void addUser(String username) {
System.out.println("User " + username + " added in a new transaction.");
throw new RuntimeException("Exception in innerMethod");
}
}解決方案
1、可以在本bean里面直接使用spring注入的方式:不過會(huì)導(dǎo)致循環(huán)依賴。
2、 AopContext.currentProxy();
@Service
public class UserServiceTxImpl implements UserServiceTx {
@Autowired
private UserServiceTxImpl userService;
@Transactional(rollbackFor = RuntimeException.class) // 默認(rèn)傳播行為為 REQUIRED
public void createUser(String username) {
System.out.println("User " + username + " created.");
// 調(diào)用另一個(gè)方法
userService.addUser(username);
System.out.println("User " + username + " createUsed ");
}
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = RuntimeException.class) // 新事務(wù)
public void addUser(String username) {
System.out.println("User " + username + " added in a new transaction.");
throw new RuntimeException("Exception in innerMethod");
}
}UserServiceTx o = (UserServiceTx) AopContext.currentProxy();
2. 直接使用 SQL 操作
如果你在事務(wù)管理的方法中執(zhí)行了原生的 SQL 操作(例如通過 JDBC),而沒有通過 Spring 的事務(wù)管理進(jìn)行處理,事務(wù)可能會(huì)失效。
@Service
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void createUser(String username) {
System.out.println("In createUser method");
// 直接使用 JDBC 操作,不通過 Spring 數(shù)據(jù)庫訪問
jdbcTemplate.execute("INSERT INTO users (username) VALUES ('" + username + "')");
// 模擬異常
throw new RuntimeException("Intentional exception"); // 模擬拋出異常
}
}這些操作不通過 Spring 的數(shù)據(jù)訪問層,將會(huì)導(dǎo)致事務(wù)管理操作失效。
3. 使用不正常的異常處理
Spring 只對(duì)運(yùn)行時(shí)異常(RuntimeException)自動(dòng)回滾,而對(duì)檢查性異常(Exception)默認(rèn)不回滾。
如果需要回滾需要rollbackFor指定回滾異常。
@Transactional(rollbackFor = RuntimeException.class) // 默認(rèn)傳播行為為 REQUIRED
public void createUser(String username) {
System.out.println("User " + username + " created.");
// 調(diào)用另一個(gè)方法
userService.addUser(username);
System.out.println("User " + username + " createUsed ");
}4、非public方法,目前事務(wù) 只支持pub

5、mysql的存儲(chǔ)引擎說mysaim不支持事務(wù)。
6、對(duì)異常進(jìn)行try catch捕獲,導(dǎo)致無法生效。
7、事務(wù)處理的響應(yīng)超時(shí)。
總結(jié)
Spring 的事務(wù)管理功能為開發(fā)者提供了一種高效且靈活的方式來處理復(fù)雜的數(shù)據(jù)庫操作,確保數(shù)據(jù)的一致性和完整性,通過注解或 XML 配置,可以很容易地查看哪些方法是事務(wù)性的,邏輯結(jié)構(gòu)清晰。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java 數(shù)據(jù)結(jié)構(gòu)進(jìn)階二叉樹題集上
二叉樹可以簡(jiǎn)單理解為對(duì)于一個(gè)節(jié)點(diǎn)來說,最多擁有一個(gè)上級(jí)節(jié)點(diǎn),同時(shí)最多具備左右兩個(gè)下級(jí)節(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu)。本文將帶你通過實(shí)際題目來熟練掌握2022-04-04
SpringBoot集成內(nèi)存數(shù)據(jù)庫hsqldb的實(shí)踐
hsqldb只需要添加對(duì)應(yīng)的依賴,然后在配置文件進(jìn)行配置。不需要安裝一個(gè)數(shù)據(jù)庫,本文就來介紹一下具體使用,感興趣的可以了解一下2021-09-09
Java多線程實(shí)現(xiàn)Runnable方式
這篇文章主要為大家詳細(xì)介紹了Java多線程如何實(shí)現(xiàn)Runnable方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03
mybatis自定義參數(shù)類型轉(zhuǎn)換器數(shù)據(jù)庫字段加密脫敏
這篇文章主要為大家介紹了mybatis自定義參數(shù)類型轉(zhuǎn)換器數(shù)據(jù)庫字段加密脫敏,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
springboot+springmvc實(shí)現(xiàn)登錄攔截
這篇文章主要介紹了springboot+springmvc實(shí)現(xiàn)登錄攔截,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10
Mybatis-Plus中and()和or()的使用與原理詳解
最近發(fā)現(xiàn)MyBatisPlus還是挺好用的,下面這篇文章主要給大家介紹了關(guān)于Mybatis-Plus中and()和or()的使用與原理的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09
當(dāng)mybatis返回值遇見內(nèi)部類的問題
這篇文章主要介紹了當(dāng)mybatis返回值遇見內(nèi)部類的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12

