🧾 一、什么是事務?
🧠 通俗理解:
事務 = 一組操作,要么全部成功,要么全部失敗,不能只做一半。
比如你轉賬:
- A 賬戶扣錢
- B 賬戶加錢
如果 A 扣了錢但 B 沒收到,那就出問題了!這就是 數據不一致,事務就是為了解決這類問題。
? 二、事務的四大特性(ACID)
特性 | 解釋 |
---|---|
原子性(Atomicity) | 要么全部成功,要么全部失敗 |
一致性(Consistency) | 操作前后數據處于一致狀態 |
隔離性(Isolation) | 多個事務互不干擾 |
持久性(Durability) | 提交后的數據是永久保存的 |
💡 三、Spring 中的事務管理方式
Spring 支持兩種事務管理方式:
1. 編程式事務管理(不常用)
手動寫事務邊界,代碼控制事務提交/回滾。
TransactionStatus status = txManager.getTransaction(...);
try {// 業務代碼txManager.commit(status);
} catch (Exception e) {txManager.rollback(status);
}
🚫 缺點:代碼侵入性強、重復、易出錯。實際開發中不推薦。
2. 聲明式事務管理 ?(主流)
通過注解或配置來管理事務,干凈、簡潔、優雅!
@Service
public class AccountService {@Transactionalpublic void transfer() {// 扣錢// 加錢// 如果中間出異常,自動回滾}
}
📌 四、@Transactional 注解詳解
@Transactional
是 Spring 中聲明事務的注解,作用范圍可以是類或方法。
常用屬性:
屬性 | 含義 |
---|---|
propagation | 事務傳播行為(默認 REQUIRED ) |
isolation | 事務隔離級別 |
rollbackFor | 指定哪些異常觸發回滾(默認是運行時異常) |
readOnly | 是否只讀事務 |
timeout | 設置事務超時時間 |
🔄 五、事務的傳播行為(重點)
行為 | 含義 |
---|---|
REQUIRED (默認) | 有事務就加入,沒有就新建 |
REQUIRES_NEW | 每次都新建一個事務,原事務掛起 |
NESTED | 嵌套事務,有獨立的回滾點 |
SUPPORTS | 有事務就用,沒有就不用 |
NOT_SUPPORTED | 不支持事務,掛起當前事務 |
NEVER | 當前不能存在事務,否則拋異常 |
MANDATORY | 必須存在事務,否則拋異常 |
🔒 六、事務隔離級別(對應數據庫的)
隔離級別 | 解決的問題 | 說明 |
---|---|---|
DEFAULT | 使用數據庫默認 | |
READ_UNCOMMITTED | 臟讀 | 最低,性能高但不安全 |
READ_COMMITTED | 不可重復讀 | 常用,如 Oracle 默認 |
REPEATABLE_READ | 幻讀 | MySQL 默認 |
SERIALIZABLE | 最強隔離 | 安全但性能差 |
💣 七、事務生效的注意事項(易踩坑)
🚨 1. 事務方法必須是 public
@Transactional // 正確
public void doSomething() {}
🚨 2. 方法 不能是同一個類中內部調用
// 錯誤示例:事務不起作用
public void outer() {this.inner(); // 沒經過代理
}
? 正確做法:通過代理調用
- 注入自身(
@Lazy
方式) - 拆到 Service 層讓 Spring 來代理調用
🚨 3. 默認只對 運行時異常 回滾
@Transactional(rollbackFor = Exception.class)
🧪 八、事務管理器(Spring 內部機制)
Spring 通過 PlatformTransactionManager 接口進行統一管理。
常用實現類:
實現類 | 場景 |
---|---|
DataSourceTransactionManager | 使用 JDBC、MyBatis |
JpaTransactionManager | 使用 JPA/Hibernate |
ChainedTransactionManager | 多數據源事務 |
? 九、一句話總結
Spring 的事務機制讓你只關心業務邏輯,不用手動管理事務,聲明式的方式簡潔高效,是真正的企業級利器。
@Transactional 實現原理
它的實現原理非常巧妙,實際上是通過 AOP(面向切面編程) 和 代理機制 實現的。這一機制讓你無需寫一行事務管理的代碼,Spring 會自動為你處理。
我們一起來詳細剖析一下底層原理!🧠
💡 一、核心原理概覽
1. AOP + 代理:
@Transactional
注解是基于 AOP(面向切面編程)實現的,具體來說,Spring 會為標記了 @Transactional
注解的方法創建一個代理對象,這個代理對象會在方法執行前后進行事務的開啟、提交和回滾等處理。
2. 動態代理:
Spring 通過動態代理技術(JDK 代理或 CGLIB 代理)生成一個代理對象,這個對象會攔截你的方法調用,在方法調用前后進行事務管理的相關操作。
3. 事務管理器:
Spring 會利用配置的 事務管理器(PlatformTransactionManager
) 來處理事務的開啟、提交、回滾等操作。
🛠? 二、@Transactional
的工作流程
步驟 1:創建代理對象
- 代理方式:Spring 使用 JDK 動態代理 或 CGLIB 代理 來生成代理對象。默認情況下,如果目標類實現了接口,Spring 會使用 JDK 動態代理;如果沒有實現接口,則使用 CGLIB 創建子類代理。
步驟 2:事務管理器的配置
@Transactional
依賴于 事務管理器 來執行事務操作,Spring 根據你配置的PlatformTransactionManager
來管理事務。常見的事務管理器有:DataSourceTransactionManager
:用于 JDBC。JpaTransactionManager
:用于 JPA(如 Hibernate)。HibernateTransactionManager
:用于 Hibernate。
步驟 3:方法執行前,開啟事務
當你調用被 @Transactional
注解標記的方法時,Spring 代理會攔截這個方法的調用,首先會判斷當前方法是否需要開啟新事務(即,方法的傳播行為是 REQUIRED
)。如果需要開啟新事務,則會通過 事務管理器 啟動一個新的事務。
- 事務的傳播行為(
propagation
):比如,REQUIRED
表示如果當前沒有事務,就新建一個事務,如果已有事務,就加入到當前事務中。
步驟 4:方法執行中,進行事務操作
方法執行過程中,Spring 會維持該事務的狀態,直到方法執行完畢。
- 事務隔離級別(
isolation
):隔離級別決定了事務之間如何互相影響(比如,是否允許臟讀、不可重復讀等)。
步驟 5:方法執行后,提交或回滾事務
- 方法正常完成:如果方法執行沒有異常,Spring 會在方法結束后提交事務。
- 方法出現異常:如果方法拋出異常,Spring 會根據
@Transactional
配置的異常回滾規則,判斷是否回滾該事務。
?? 三、事務管理的關鍵組件
1. TransactionInterceptor:核心事務攔截器
- Spring 使用
TransactionInterceptor
來處理所有帶有@Transactional
注解的方法,它是事務管理的關鍵攔截器。 - 該攔截器負責從容器中獲取事務管理器、根據事務配置設置事務屬性(如隔離級別、傳播行為等),并且控制事務的開啟、提交和回滾。
2. TransactionProxyFactoryBean:生成代理對象
TransactionProxyFactoryBean
是 Spring 用來創建事務代理的工廠,實際上它會根據注解或配置生成一個代理對象。- 這個代理對象會攔截對事務方法的調用,并在調用方法前后進行事務的處理。
3. PlatformTransactionManager:事務管理器
- 事務管理器(如
DataSourceTransactionManager
)負責實際的事務操作,包括開啟事務、提交事務、回滾事務等。
🔍 四、事務處理的實際步驟
- 方法調用被代理對象攔截
- 被
@Transactional
標記的方法會被 AOP 代理攔截,調用TransactionInterceptor
。
- 被
- 事務攔截器執行邏輯
TransactionInterceptor
會根據@Transactional
配置,從容器中獲取 事務管理器(PlatformTransactionManager
)。
- 判斷事務傳播行為
- 如果當前沒有事務(例如沒有正在進行的事務),根據傳播行為決定是否新建事務。默認是
REQUIRED
,即如果沒有事務,創建一個新的事務。
- 如果當前沒有事務(例如沒有正在進行的事務),根據傳播行為決定是否新建事務。默認是
- 開啟事務
- 通過
PlatformTransactionManager
開啟一個事務(實際是對底層數據庫操作的封裝)。
- 通過
- 執行目標方法
- 目標方法執行,在方法內的操作都在同一個事務中進行。
- 提交或回滾事務
- 如果方法執行沒有拋出異常,Spring 提交事務;如果拋出指定異常(如運行時異常),Spring 會回滾事務。
💡 五、示例:@Transactional
事務的底層工作
假設你有一個服務類 AccountService
,方法 transfer()
被 @Transactional
注解標記:
@Service
public class AccountService {@Transactionalpublic void transfer() {// 扣款操作// 加款操作// 如果中間出錯,Spring 會自動回滾}
}
- Spring 會生成一個代理對象
AccountService
,攔截transfer()
方法調用。 - 代理對象通過
TransactionInterceptor
來控制事務的開啟、提交或回滾。 - 在調用
transfer()
方法之前,Spring 會檢查是否需要開啟事務,創建事務并將其綁定到當前線程(一般是通過ThreadLocal
來綁定)。 transfer()
執行完后,如果沒有異常,Spring 會提交事務;如果出現異常,Spring 會根據配置回滾事務。
🧠 六、總結
@Transactional
的底層原理依賴于 AOP + 動態代理,通過 事務攔截器(TransactionInterceptor
)和 事務管理器(PlatformTransactionManager
)來實現事務的控制。Spring 提供了靈活的事務傳播行為和隔離級別,讓事務控制變得更加簡單和清晰。
Spring 事務在什么情況下會失效?
1. 方法是 private
或 protected
Spring 的事務管理是基于 AOP(面向切面編程) 實現的,而 AOP 需要代理對象來執行方法。如果方法是 private
或 protected
,Spring 的 AOP 代理是無法攔截到該方法的,因此事務無法生效。
解決方法:
- 將事務方法設置為
public
,這樣 Spring 的代理對象才能正確地攔截并應用事務。
@Transactional
public void transfer() { // 必須是 public// 業務邏輯
}
2. 在同一類內調用事務方法
當一個類內部的事務方法相互調用時,事務是不會生效的。這是因為 事務代理 是基于代理對象的,而類內部方法調用是直接調用對象的方法,不會經過代理。
例子:
@Service
public class AccountService {@Transactionalpublic void transfer() {// 扣款操作this.otherMethod(); // 調用同一個類中的方法}@Transactionalpublic void otherMethod() {// 加款操作}
}
在這個例子中,transfer()
調用了 otherMethod()
,但 @Transactional
注解沒有生效,因為調用 this.otherMethod()
是直接調用類內部的方法,不會經過代理。
解決方法:
- 拆分方法,將事務性方法提取到另一個服務類中,確保 Spring 代理對象能夠接管方法調用。
3. 沒有配置事務管理器
Spring 中的事務是依賴于 PlatformTransactionManager
來進行管理的。如果沒有配置或注入正確的事務管理器,事務就無法生效。
解決方法:
- 確保在
applicationContext.xml
或Spring Boot
配置類中配置了正確的事務管理器。
Spring Boot 示例:
@Configuration
@EnableTransactionManagement
public class TransactionConfig {@Beanpublic PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {return new JpaTransactionManager(entityManagerFactory);}
}
4. 事務方法拋出了非 RuntimeException
異常
Spring 默認只對 運行時異常 (RuntimeException
和其子類) 回滾事務。如果你在方法中拋出了一個 檢查型異常(checked exception
)(如 IOException
、SQLException
等),默認情況下,Spring 不會回滾事務。
解決方法:
- 使用
@Transactional
的rollbackFor
屬性來指定哪些異常需要回滾事務。
@Transactional(rollbackFor = Exception.class) // 指定回滾異常
public void transfer() throws IOException {// 業務邏輯
}
5. 事務被 @Transactional
注解的代理方法外部調用
Spring 的事務管理依賴于 代理對象,如果事務方法被其他 非 Spring 管理的對象調用,Spring 的事務也不會生效。
解決方法:
- 確保事務方法僅通過 Spring 管理的代理對象來調用。避免手動調用
new
創建的對象或外部非 Spring 管理的對象。
6. 事務的傳播行為不正確
事務的傳播行為(propagation
)控制了事務的嵌套和傳播方式。如果設置不當,事務可能不會按預期工作。
例如,如果你使用了 REQUIRES_NEW
傳播行為,每次方法都會啟動一個新的事務,原事務會被掛起。如果這時原事務出現問題,它就無法回滾。
解決方法:
- 確保事務的傳播行為與業務場景相匹配。常見的設置是
REQUIRED
(默認),它會加入當前事務,如果沒有事務,則創建一個新的事務。
@Transactional(propagation = Propagation.REQUIRED)
public void transfer() {// 事務邏輯
}
7. 非事務方法調用事務方法時,事務失效
如果一個非事務方法調用了帶有 @Transactional
的方法,在一些情況下事務不會生效。特別是在 非 Spring 管理的對象 上,事務不會生效。
解決方法:
- 確保事務方法是由 Spring 管理的代理對象來調用,避免在非 Spring 管理的對象中調用事務方法。
8. 只讀事務修改數據
@Transactional
中的 readOnly
屬性告訴 Spring 該事務是只讀的。如果在只讀事務中執行了寫操作(比如更新數據庫),在某些數據庫系統中事務可能不會按預期提交,甚至會拋出異常。
解決方法:
- 確保只讀事務只用于查詢操作,避免在只讀事務中執行寫操作。
@Transactional(readOnly = true)
public List<User> getAllUsers() {return userRepository.findAll();
}
🚦 九、總結
Spring 事務失效的常見原因:
- 事務方法是
private
或protected
。 - 方法在同一類中被調用(直接調用,未通過代理)。
- 沒有配置事務管理器。
- 拋出了非
RuntimeException
異常。 - 事務方法被非 Spring 管理的對象外部調用。
- 事務的傳播行為設置不當。
- 非事務方法調用事務方法時,事務失效。
- 只讀事務進行數據修改。
避免失效的建議:
- 確保事務方法是
public
。 - 使用 Spring 管理的代理對象來調用事務方法。
- 正確配置事務管理器。
- 根據需求調整
rollbackFor
和propagation
配置。 - 使用
readOnly
標記僅進行查詢的事務。