透徹的掌握 Spring 中 @transactional 的使用
你知道的越多,不知道的就越多,業(yè)余的像一棵小草!
你來,我們一起精進!你不來,我和你的競爭對手一起精進!
編輯:業(yè)余草
推薦:https://www.xttblog.com/?p=5263
透徹的掌握 Spring 中 @transactional 的使用
事務管理是應用系統(tǒng)開發(fā)中必不可少的一部分。Spring 為事務管理提供了豐富的功能支持。

聲明式事務有兩種方式,一種是在配置文件(xml)中做相關的事務規(guī)則聲明,另一種是基于 「@Transactional」 注解的方式。注釋配置是目前流行的使用方式。
一、@Transactional 注解管理事務的實現步驟
使用 @Transactional 注解管理事務的實現步驟分為兩步。
第一步,在 xml 配置文件中添加事務配置信息。
<tx:annotation-driven />
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
第二步,將 @Transactional 注解添加到合適的方法上,并設置合適的屬性信息。
@Transactional 注解的屬性信息
| 屬性名 | 說明 |
|---|---|
| name | 當在配置文件中有多個 TransactionManager , 可以用該屬性指定選擇哪個事務管理器。 |
| propagation | 事務的傳播行為,默認值為 REQUIRED。 |
| isolation | 事務的隔離度,默認值采用 DEFAULT。 |
| timeout | 事務的超時時間,默認值為-1。如果超過該時間限制但事務還沒有完成,則自動回滾事務。 |
| read-only | 指定事務是否為只讀事務,默認值為 false;為了忽略那些不需要事務的方法,比如讀取數據,可以設置 read-only 為 true。 |
| rollback-for | 用于指定能夠觸發(fā)事務回滾的異常類型,如果有多個異常類型需要指定,各類型之間可以通過逗號分隔。 |
| no-rollback-for | 拋出 no-rollback-for 指定的異常類型,不回滾事務。 |
除此以外,@Transactional 注解也可以添加到類級別上。當把 @Transactional 注解放在類級別時,表示所有該類的公共方法都配置相同的事務屬性信息。
方法級別的事務屬性信息會覆蓋類級別的相關配置信息。
@Transactional 注解的標注于類上:
@Transactional(propagation= Propagation.SUPPORTS,readOnly=true)
@Service(value ="employeeService")
public class EmployeeService
二、注解方式的事務使用注意事項
當您對 Spring 的基于注解方式的實現步驟和事務內在實現機制有較好的理解之后,就會更好的使用注解方式的事務管理,避免當系統(tǒng)拋出異常,數據不能回滾的問題。

1. 正確的設置@Transactional 的 propagation 屬性
本來期望目標方法進行事務管理,但若是錯誤的配置這三種 propagation,事務將不會發(fā)生回滾。
TransactionDefinition.PROPAGATION_SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續(xù)運行。TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務方式運行,如果當前存在事務,則把當前事務掛起。TransactionDefinition.PROPAGATION_NEVER:以非事務方式運行,如果當前存在事務,則拋出異常。
2. 正確的設置 @Transactional 的 rollbackFor 屬性
默認情況下,如果在事務中拋出了未檢查異常(繼承自 RuntimeException 的異常)或者 Error,則 Spring 將回滾事務;除此之外的異常,Spring 都不會回滾事務!
如果在事務中拋出其他類型的異常,并期望 Spring 能夠回滾事務,可以指定 rollbackFor。例:
@Transactional(propagation= Propagation.REQUIRED, rollbackFor= MyException.class)
若在目標方法中拋出的異常是 rollbackFor 指定的異常的子類,事務同樣會回滾。
3. @Transactional 只能應用到 public 方法才有效
只有 @Transactional 注解應用到 public 方法,才能進行事務管理。
Spring AOP 會檢查目標方法的修飾符是不是 public,若不是 public,就不會獲取 @Transactional 的屬性配置信息,最終會造成不會用 TransactionInterceptor 來攔截該目標方法進行事務管理。
4. 避免 Spring 的 AOP 的自調用問題
在 Spring 的 AOP 代理下,只有目標方法由外部調用,目標方法才由 Spring 生成的代理對象來管理,這會造成自調用問題。
若同一類中的其他沒有 @Transactional 注解的方法內部調用有 @Transactional 注解的方法,有 @Transactional 注解的方法的事務被忽略,不會發(fā)生回滾。
@Service
public class OrderService {
private void insert() {
insertOrder();
}
@Transactional
public void insertOrder() {
//insert log info
//insertOrder
//updateAccount
}
}
insertOrder 盡管有@Transactional 注解,但它被內部方法 insert 調用,事務被忽略,出現異常事務不會發(fā)生回滾。

2. 三大問題
數據庫事務的正確執(zhí)行的 4 個基本要素是 原子性(「A」tommicity)、一致性(「C」onsistency)、隔離性(「I」solation) 和 持久性(「D」urability)。
原子性 : 整個事務中的操作,要么全部完成,要么全部不完成,不可能停滯在中間某個環(huán)節(jié),僅完成一部分。
一致性 : 指一個事務可以改變數據庫狀態(tài)。食物必須始終保持系統(tǒng)處于一致狀態(tài),不管任何給定時間有多少并發(fā)事務。
隔離性 : 之兩個事務之間的隔離級別。
持久性 : 在事務完成以后,該事務對數據庫所做的更改便持久保存在數據庫中,并不會被回滾。
三大問題從 嚴重 到 輕度 以此如下:
「臟讀問題」
| 時刻 | 事務一(老公) | 事務二(老婆) |
|---|---|---|
| T1 | 查詢余額,顯示10k | —— |
| T2 | —— | 查詢余額,顯示 10k |
| T3 | —— | 網購 1千,顯示 9k |
| T4 | 請客吃飯開銷 1k,顯示余額 8k | —— |
| T5 | 提交事務 | —— |
| T6 | —— | 回滾事務 |
| T7 | —— | 最終余額 8k |
所謂臟讀,指的就是讀到了“臟”數據,即一個事務讀取到了另一個事務未提交的數據。
「不可重復讀問題」
| 時刻 | 事務一(老公) | 事務二(老婆) |
|---|---|---|
| T1 | 查詢余額,顯示10k | —— |
| T2 | —— | 查詢余額,顯示 10k |
| T3 | —— | 網購,開銷 1k,余額 9k |
| T4 | 請客吃飯,預計開銷 2k | —— |
| T5 | —— | 網購,開銷 8k,余額 1k |
| T6 | —— | 提交事務 |
| T7 | 吃完買單,顯示余額1k,不夠付賬。 | —— |
不可重復讀,指的是理論上的同一條數據,重復讀取,居然會不一樣,不具備可重復性。
「幻讀問題」
| 時刻 | 事務一(老公) | 事務二(老婆) |
|---|---|---|
| T1 | —— | 查詢信用卡消費記錄,顯示 10 條記錄 |
| T2 | 網購 | —— |
| T3 | 提交事務 | —— |
| T4 | —— | 打印消費記錄,有11條記錄 |
幻讀,和不可重復讀類似,第二次讀取的數據相較于第一次居然發(fā)生了變化,仿佛看到了幻覺。
不可重復讀 和 幻讀 有一定的相似性,都是指(在本人未改變的情況下)第二次讀取的數據,與第一次讀取結果不一樣。不過它們描述的側重點(及造成的影響程度)不一樣。
不可重復讀問題,強調的是某一條數據的內容在“我”兩次讀取間,發(fā)生了改變。(因為 update 語句) 幻讀問題,強調的整個數據的數據總量,在“我”兩次讀取間,發(fā)生了改變。(因為 insert / delete 語句) 幻讀問題 造成的危害要小于 不可重復讀問題。
3. 四個隔離級別
隔離級別表示:「當“我”操作這張表時,“其他人”對這張表還有多大的操作權限」 ?!拔摇钡母綦x級別越高,其他人的權利就越小,那么“他”要執(zhí)行他想要執(zhí)行的操作而沒有權限時,那就只能 「等」“我”操作完 。
數據庫領域有四個隔離級別(注意,這并非 Java 中特有的概念),針對于上述三大問題,四個隔離級別,從 解決不了任何問題 到 解決所有問題,每一級多解決一個問題。
| 隔離級別 | 解決問題 | 備注 |
|---|---|---|
| READ_UNCOMMITTED | 解決不了任何問題 | —— |
| READ_COMMITTED | 可以解決臟讀問題 | —— |
| REPEATABLE_READ | 可以解決不可重復讀問題 | 包括解決臟讀問題 |
| SERIALIZABLE | 解決幻讀問題 | 包括解決不可重復讀和臟讀問題 |
4. 傳播機制
| 傳播行為 | 含義 | 備注 |
|---|---|---|
| REQUIRED | 當方法調用時,如果不存在當前事務,那么就創(chuàng)建事務;如果之前已經存在了事物,那么就沿用之前的事務。 | 默認值 |
| SUPPORTS | 當方法調用時,如果不存在當前事務,就不啟用事務;如果當前啟用事務,那么就沿用當前事務。 | —— |
| MANATORY | 方法必須在事務內運行。 | 如果不存在當前事務,則直接拋出異常。 |
| REQUIRES_NEW | 無論是否存在當前事務,方法都會在新的事務中運行 | 總是開啟一個新事務,執(zhí)行本方法。 |
| NOT_SUPPORTED | 不支持事務,不存在當前事務也不會創(chuàng)建新事務;如果存在當前事務則掛起它,直到方法結束后才恢復當前事務 | 適用于那些不支持事務的數據庫和SQL語句 |
| NEVER | 不支持事務。 | MANATORY 的“反面”,如果存在當前事務,就直接拋出異常。 |
| NESTED | 嵌套事務。REQUIRES_NEW 的高級版 | 支持當前事務中使用保存點(savepoint),可以回滾到保存點;如果當前事務沒有保存點,則完全等價于 REQUIRES_NEW |
毫無疑問,最常用的是 REQUIRED,其次是 REQUIRES_NEW 。它們是最常見的業(yè)務處理方式,其它方式都是用于處理特定的業(yè)務。
傳播機制本質上描述的是:在一個整體行為中,一個部分行為的失敗,會不會對整體行為造成影響,以及造成何種影響。
