目錄
- 一、概述
- 二、事務的特性(ACID)
- 三、Spring 的事務管理
- 3.1 編程式事務管理
- 3.2 編程式事務管理
- 四、Spring 事務管理接口及其定義的屬性
- 4.1 PlatformTransactionManager:事務管理接口
- 4.2 TransactionDefinition:事務屬性
- 4.3 TransactionStatus:事務狀態
- 五、Spring 事務屬性
- 5.1 事務傳播行為
- 5.2 事務隔離級別
- 5.3 事務超時屬性
- 5.4 事務只讀屬性
- 5.5 事務回滾規則
- 六、事務中的 @Transactional 注解
- 6.1 @Transactional 的作用范圍
- 6.2 @Transactional 的常用配置參數
- 6.3 @Transactional 事務注解原理
- 6.4 Spring AOP 自調用問題
- 6.5 @Transactional 的使用注意事項總結
一、概述
事務是邏輯上的一組操作,要么都執行,要么都不執行。
我們系統的每個業務方法可能包括了多個原子性的數據庫操作,比如下面的 savePerson() 方法中就有兩個原子性的數據庫操作。這些原子性的數據庫操作是有依賴的,它們要么都執行,要不就都不執行。
public void savePerson(PersonEntity person, PersonDetailEntity personDetail) {personDao.save(person);personDetailDao.save(personDetail);}
另外,需要格外注意的是:事務能否生效數據庫引擎是否支持事務是關鍵。比如常用的 MySQL 數據庫默認使用支持事務的 innodb引擎。但是,如果把數據庫引擎變為 myisam,那么程序也就不再支持事務了!
事務最經典也經常被拿出來說例子就是轉賬了。假如小明要給小紅轉賬 1000 元,這個轉賬會涉及到兩個關鍵操作就是:
將小明的余額減少 1000 元。
將小紅的余額增加 1000 元。
萬一在這兩個操作之間突然出現錯誤比如銀行系統崩潰或者網絡故障,導致小明余額減少而小紅的余額沒有增加,這樣就不對了。事務就是保證這兩個關鍵操作要么都成功,要么都要失敗。
public class OrdersService {private AccountDao accountDao;public void setOrdersDao(AccountDao accountDao) {this.accountDao = accountDao;}@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT, readOnly = false, timeout = -1)public void accountMoney() {//小紅賬戶多1000accountDao.addMoney(1000,xiaohong);//模擬突然出現的異常,比如銀行中可能為突然停電等等//如果沒有配置事務管理的話會造成,小紅賬戶多了1000而小明賬戶沒有少錢int i = 10 / 0;//小王賬戶少1000accountDao.reduceMoney(1000,xiaoming);}
}
二、事務的特性(ACID)
- 原子性(Atomicity):事務是最小的執行單位,不允許分割。事務的原子性確保動作要么全部完成,要么完全不起作用;
- 一致性(Consistency):執行事務前后,數據保持一致,例如轉賬業務中,無論事務是否成功,轉賬者和收款人的總額應該是不變的;
- 隔離性(Isolation):并發訪問數據庫時,一個用戶的事務不被其他事務所干擾,各并發事務之間數據庫是獨立的;
- 持久性(Durability):一個事務被提交之后。它對數據庫中數據的改變是持久的,即使數據庫發生故障也不應該對其有任何影響。
這里要補充一點:只有保證了事務的持久性、原子性、隔離性之后,一致性才能得到保障。也就是說 A、I、D 是手段,C 是目的!
三、Spring 的事務管理
**MySQL 保證原子性機制:**如果想要保證事務的原子性,就需要在異常發生時,對已經執行的操作進行回滾,在 MySQL 中,恢復機制是通過 回滾日志(undo log) 實現的,所有事務進行的修改都會先記錄到這個回滾日志中,然后再執行相關的操作。如果執行過程中遇到異常的話,我們直接利用 回滾日志 中的信息將數據回滾到修改之前的樣子即可!并且,回滾日志會先于數據持久化到磁盤上。這樣就保證了即使遇到數據庫突然宕機等情況,當用戶再次啟動數據庫的時候,數據庫還能夠通過查詢回滾日志來回滾之前未完成的事務。
3.1 編程式事務管理
通過 TransactionTemplate或者TransactionManager手動管理事務,實際應用中很少使用,但是對于理解 Spring 事務管理原理有幫助。
使用TransactionTemplate 進行編程式事務管理的示例代碼如下:
@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {transactionTemplate.execute(new TransactionCallbackWithoutResult() {@Overrideprotected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {try {// .... 業務代碼} catch (Exception e){//回滾transactionStatus.setRollbackOnly();}}});
}
使用 TransactionManager 進行編程式事務管理的示例代碼如下:
@Autowired
private PlatformTransactionManager transactionManager;public void testTransaction() {TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());try {// .... 業務代碼transactionManager.commit(status);} catch (Exception e) {transactionManager.rollback(status);}
}
3.2 編程式事務管理
推薦使用(代碼侵入性最小),實際是通過 AOP 實現(基于@Transactional 的全注解方式使用最多)。
使用 @Transactional注解進行事務管理的示例代碼如下:
@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {//do somethingB b = new B();C c = new C();b.bMethod();c.cMethod();
}
四、Spring 事務管理接口及其定義的屬性
Spring 框架中,事務管理相關最重要的 3 個接口如下:
- PlatformTransactionManager:(平臺)事務管理器,Spring 事務策略的核心。
- TransactionDefinition:事務定義信息(事務隔離級別、傳播行為、超時、只讀、回滾規則)。
- TransactionStatus:事務運行狀態。
我們可以把 PlatformTransactionManager 接口看作是事務上層的管理者,而 TransactionDefinition 和 TransactionStatus 這兩個接口可以看作是事務的描述。PlatformTransactionManager 會根據 TransactionDefinition 的定義比如事務超時時間、隔離級別、傳播行為等來進行事務管理 ,而 TransactionStatus 接口則提供了一些方法來獲取事務相應的狀態比如是否新事務、是否可以回滾等等。
4.1 PlatformTransactionManager:事務管理接口
Spring 并不直接管理事務,而是提供了多種事務管理器 。Spring 事務管理器的接口是:PlatformTransactionManager 。通過這個接口,Spring 為各個平臺如:JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了對應的事務管理器,但是具體的實現就是各個平臺自己的事情了。
PlatformTransactionManager 接口的具體實現如下:
PlatformTransactionManager接口中定義了三個方法:
package org.springframework.transaction;import org.springframework.lang.Nullable;public interface PlatformTransactionManager {//獲得事務TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;//提交事務void commit(TransactionStatus var1) throws TransactionException;//回滾事務void rollback(TransactionStatus var1) throws TransactionException;
}
4.2 TransactionDefinition:事務屬性
事務屬性可以理解成事務的一些基本配置,描述了事務策略如何應用到方法上。
事務管理器接口 PlatformTransactionManager 通過 getTransaction(TransactionDefinition definition) 方法來得到一個事務,這個方法里面的參數是 TransactionDefinition 類 ,這個類就定義了一些基本的事務屬性。事務屬性包含了 5 個方面:
- 隔離級別
- 傳播行為
- 回滾規則
- 是否只讀
- 事務超時
TransactionDefinition 接口中定義了 5 個方法以及一些表示事務屬性的常量比如隔離級別、傳播行為等等。
package org.springframework.transaction;import org.springframework.lang.Nullable;public interface TransactionDefinition {int PROPAGATION_REQUIRED = 0;int PROPAGATION_SUPPORTS = 1;int PROPAGATION_MANDATORY = 2;int PROPAGATION_REQUIRES_NEW = 3;int PROPAGATION_NOT_SUPPORTED = 4;int PROPAGATION_NEVER = 5;int PROPAGATION_NESTED = 6;int ISOLATION_DEFAULT = -1;int ISOLATION_READ_UNCOMMITTED = 1;int ISOLATION_READ_COMMITTED = 2;int ISOLATION_REPEATABLE_READ = 4;int ISOLATION_SERIALIZABLE = 8;int TIMEOUT_DEFAULT = -1;// 返回事務的傳播行為,默認值為 REQUIRED。int getPropagationBehavior();//返回事務的隔離級別,默認值是 DEFAULTint getIsolationLevel();// 返回事務的超時時間,默認值為-1。如果超過該時間限制但事務還沒有完成,則自動回滾事務。int getTimeout();// 返回是否為只讀事務,默認值為 falseboolean isReadOnly();@NullableString getName();
}
4.3 TransactionStatus:事務狀態
TransactionStatus接口用來記錄事務的狀態,該接口定義了一組方法,用來獲取或判斷事務的相應狀態信息。PlatformTransactionManager.getTransaction(…)方法返回一個 TransactionStatus 對象。
TransactionStatus 接口內容如下:
public interface TransactionStatus{boolean isNewTransaction(); // 是否是新的事務boolean hasSavepoint(); // 是否有恢復點void setRollbackOnly(); // 設置為只回滾boolean isRollbackOnly(); // 是否為只回滾boolean isCompleted; // 是否已完成
}
五、Spring 事務屬性
實際業務開發中,大家一般都是使用 @Transactional 注解來開啟事務,很多人并不清楚這個參數里面的參數是什么意思,有什么用。
5.1 事務傳播行為
事務傳播行為是為了解決業務層方法之間互相調用的事務問題。 當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運行,也可能開啟一個新事務,并在自己的事務中運行。
舉個例子:我們在 A 類的aMethod()方法中調用了 B 類的 bMethod() 方法。這個時候就涉及到業務層方法之間互相調用的事務問題。如果我們的 bMethod()如果發生異常需要回滾,如何配置事務傳播行為才能讓 aMethod()也跟著回滾呢?
@Service
Class A {@AutowiredB b;@Transactional(propagation = Propagation.xxx)public void aMethod {b.bMethod();}
}@Service
Class B {@Transactional(propagation = Propagation.xxx)public void bMethod {//do something}
}
在TransactionDefinition定義中包括了如下幾個表示傳播行為的常量:
public interface TransactionDefinition {int PROPAGATION_REQUIRED = 0;int PROPAGATION_SUPPORTS = 1;int PROPAGATION_MANDATORY = 2;int PROPAGATION_REQUIRES_NEW = 3;int PROPAGATION_NOT_SUPPORTED = 4;int PROPAGATION_NEVER = 5;int PROPAGATION_NESTED = 6;......
}
不過,為了方便使用,Spring 相應地定義了一個枚舉類:Propagation
package org.springframework.transaction.annotation;import org.springframework.transaction.TransactionDefinition;public enum Propagation {REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),NEVER(TransactionDefinition.PROPAGATION_NEVER),NESTED(TransactionDefinition.PROPAGATION_NESTED);private final int value;Propagation(int value) {this.value = value;}public int value() {return this.value;}
}
正確的事務傳播行為可能的值如下:
-
TransactionDefinition.PROPAGATION_REQUIRED
使用的最多的一個事務傳播行為,我們平時經常使用的@Transactional注解默認使用就是這個事務傳播行為。如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。也就是說:- 如果外部方法沒有開啟事務的話,Propagation.REQUIRED修飾的內部方法會新開啟自己的事務,且開啟的事務相互獨立,互不干擾。
- 如果外部方法開啟事務并且被Propagation.REQUIRED的話,所有Propagation.REQUIRED修飾的內部方法和外部方法均屬于同一事務 ,只要一個方法回滾,整個事務均回滾。
舉個例子:如果我們上面的aMethod()和bMethod()使用的都是PROPAGATION_REQUIRED傳播行為的話,兩者使用的就是同一個事務,只要其中一個方法回滾,整個事務均回滾。
@Service Class A {@AutowiredB b;@Transactional(propagation = Propagation.REQUIRED)public void aMethod {//do somethingb.bMethod();} } @Service Class B {@Transactional(propagation = Propagation.REQUIRED)public void bMethod {//do something} }
-
TransactionDefinition.PROPAGATION_REQUIRES_NEW
創建一個新的事務,如果當前存在事務,則把當前事務掛起。也就是說不管外部方法是否開啟事務,Propagation.REQUIRES_NEW修飾的內部方法會新開啟自己的事務,且開啟的事務相互獨立,互不干擾。舉個例子:如果我們上面的bMethod()使用PROPAGATION_REQUIRES_NEW事務傳播行為修飾,aMethod還是用PROPAGATION_REQUIRED修飾的話。如果aMethod()發生異常回滾,bMethod()不會跟著回滾,因為 bMethod()開啟了獨立的事務。但是,如果 bMethod()拋出了未被捕獲的異常并且這個異常滿足事務回滾規則的話,aMethod()同樣也會回滾,因為這個異常被 aMethod()的事務管理機制檢測到了。
@Service Class A {@AutowiredB b;@Transactional(propagation = Propagation.REQUIRED)public void aMethod {//do somethingb.bMethod();} }@Service Class B {@Transactional(propagation = Propagation.REQUIRES_NEW)public void bMethod {//do something} }
-
TransactionDefinition.PROPAGATION_NESTED
如果當前存在事務,就在嵌套事務內執行;如果當前沒有事務,就執行與TransactionDefinition.PROPAGATION_REQUIRED類似的操作。也就是說:- 在外部方法開啟事務的情況下,在內部開啟一個新的事務,作為嵌套事務存在。
- 如果外部方法無事務,則單獨開啟一個事務,與 PROPAGATION_REQUIRED 類似。
舉個例子:如果 bMethod() 回滾的話,aMethod()不會回滾。如果 aMethod() 回滾的話,bMethod()會回滾。
@Service Class A {@AutowiredB b;@Transactional(propagation = Propagation.REQUIRED)public void aMethod {//do somethingb.bMethod();} }@Service Class B {@Transactional(propagation = Propagation.NESTED)public void bMethod {//do something} }
-
TransactionDefinition.PROPAGATION_MANDATORY
如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。(mandatory:強制性)這個使用的很少。若是錯誤的配置以下 3 種事務傳播行為,事務將不會發生回滾,這里不對照案例講解了,使用的很少。
- TransactionDefinition.PROPAGATION_SUPPORTS: 如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事務方式運行,如果當前存在事務,則把當前事務掛起。
- TransactionDefinition.PROPAGATION_NEVER: 以非事務方式運行,如果當前存在事務,則拋出異常。
5.2 事務隔離級別
TransactionDefinition 接口中定義了五個表示隔離級別的常量:
public interface TransactionDefinition {......int ISOLATION_DEFAULT = -1;int ISOLATION_READ_UNCOMMITTED = 1;int ISOLATION_READ_COMMITTED = 2;int ISOLATION_REPEATABLE_READ = 4;int ISOLATION_SERIALIZABLE = 8;......
}
和事務傳播行為那塊一樣,為了方便使用,Spring 也相應地定義了一個枚舉類:Isolation
public enum Isolation {DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);private final int value;Isolation(int value) {this.value = value;}public int value() {return this.value;}
}
各種事務隔離級別如下:
- TransactionDefinition.ISOLATION_DEFAULT :使用后端數據庫默認的隔離級別,MySQL 默認采用的 REPEATABLE_READ 隔離級別, Oracle 默認采用的 READ_COMMITTED 隔離級別。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED :最低的隔離級別,使用這個隔離級別很少,因為它允許讀取尚未提交的數據變更,可能會導致臟讀、幻讀或不可重復讀。
- TransactionDefinition.ISOLATION_READ_COMMITTED : 允許讀取并發事務已經提交的數據,可以阻止臟讀,但是幻讀或不可重復讀仍有可能發生。
- TransactionDefinition.ISOLATION_REPEATABLE_READ : 對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改,可以阻止臟讀和不可重復讀,但幻讀仍有可能發生。
- TransactionDefinition.ISOLATION_SERIALIZABLE : 最高的隔離級別,完全服從 ACID 的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,**該級別可以防止臟讀、不可重復讀以及幻讀,但是這將嚴重影響程序的性能。**通常情況下也不會用到該級別。
5.3 事務超時屬性
所謂事務超時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒,默認值為-1,這表示事務的超時時間取決于底層事務系統或者沒有超時時間。
5.4 事務只讀屬性
對于只有讀取數據查詢的事務,可以指定事務類型為 readonly,即只讀事務。只讀事務不涉及數據的修改,數據庫會提供一些優化手段,適合用在有多條數據庫查詢操作的方法中。
package org.springframework.transaction;import org.springframework.lang.Nullable;public interface TransactionDefinition {......// 返回是否為只讀事務,默認值為 falseboolean isReadOnly();
}
MySQL 默認對每一個新建立的連接都啟用了autocommit模式。
在該模式下,每一個發送到 MySQL 服務器的sql語句都會在一個單獨的事務中進行處理,
執行結束后會自動提交事務,并開啟一個新的事務。
- 如果給方法加上了Transactional注解,這個方法執行的所有sql會被放在一個事務中。如果聲明了只讀事務的話,數據庫就會去優化它的執行,并不會帶來其他的什么收益。
- 如果不加Transactional,每條sql會開啟一個單獨的事務,中間被其它事務改了數據,都會實時讀取到最新值。
- 如果你一次執行單條查詢語句,則沒有必要啟用事務支持,數據庫默認支持 SQL 執行期間的讀一致性。
- 如果你一次執行多條查詢語句,例如統計查詢、報表查詢等。在這種情況下,多條查詢 SQL 必須保證整體的讀一致性;否則,在前條 SQL 查詢之后,后條 SQL 查詢之前,數據被其他用戶改變,則該次整體的統計查詢將會出現讀數據不一致的狀態,此時,應該啟用事務支持。
5.5 事務回滾規則
這些規則定義了哪些異常會導致事務回滾而哪些不會。默認情況下,事務只有遇到運行期異常(RuntimeException 的子類)時才會回滾,Error 也會導致事務回滾,但是,在遇到檢查型(Checked)異常時不會回滾。如果你想要回滾你定義的特定的異常類型的話,可以這樣:
@Transactional(rollbackFor= MyException.class)
六、事務中的 @Transactional 注解
6.1 @Transactional 的作用范圍
- 方法:推薦將注解使用于方法上,不過需要注意的是:該注解只能應用到 public 方法上,否則不生效。
- 類:如果這個注解使用在類上的話,表明該注解對該類中所有的 public 方法都生效。
- 接口:不推薦在接口上使用。
6.2 @Transactional 的常用配置參數
屬性 | 名說明 |
---|---|
propagation | 事務的傳播行為,默認值為 REQUIRED,可選的值在上面介紹過 |
isolation | 事務的隔離級別,默認值采用 DEFAULT,可選的值在上面介紹過 |
timeout | 事務的超時時間,默認值為-1(不會超時)。如果超過該時間限制但事務還沒有完成,則自動回滾事務。 |
readOnly | 指定事務是否為只讀事務,默認值為 false。 |
rollbackFor | 用于指定能夠觸發事務回滾的異常類型,并且可以指定多個異常類型。 |
6.3 @Transactional 事務注解原理
@Transactional 的工作機制是基于 AOP 實現的,AOP 又是使用動態代理實現的。如果目標對象實現了接口,默認情況下會采用 JDK 的動態代理,如果目標對象沒有實現了接口,會使用 CGLIB 動態代理。
如果一個類或者一個類中的 public 方法上被標注@Transactional 注解的話,Spring 容器就會在啟動的時候為其創建一個代理類,在調用被@Transactional 注解的 public 方法的時候,實際調用的是,TransactionInterceptor 類中的 invoke()方法。這個方法的作用就是在目標方法之前開啟事務,方法執行過程中如果遇到異常的時候回滾事務,方法調用完成之后提交事務。
TransactionInterceptor 類中的 invoke()方法內部實際調用的是 TransactionAspectSupport 類的 invokeWithinTransaction()方法。
6.4 Spring AOP 自調用問題
當一個方法被標記了@Transactional 注解的時候,Spring 事務管理器只會在被其他類方法調用的時候生效,而不會在一個類中方法調用生效。
這是因為 Spring AOP 工作原理決定的。 Spring AOP 使用動態代理來實現事務的管理,它會在運行的時候為帶有 @Transactional 注解的方法生成代理對象,并在方法調用的前后應用事物邏輯。如果該方法被其他類調用我們的代理對象就會攔截方法調用并處理事務。但是在一個類中的其他方法內部調用的時候,我們代理對象就無法攔截到這個內部調用,因此事務也就失效了。
例如:MyService 類中的method1()調用method2()就會導致method2()的事務失效。
@Service
public class MyService {private void method1() {method2();//......
}
@Transactionalpublic void method2() {//......}
}
解決辦法就是避免同一類中自調用或者使用 AspectJ 取代 Spring AOP 代理。
6.5 @Transactional 的使用注意事項總結
- @Transactional 注解只有作用到 public 方法上事務才生效,不推薦在接口上使用;
- 避免同一個類中調用 @Transactional 注解的方法,這樣會導致事務失效;
- 正確的設置 @Transactional 的 rollbackFor 和 propagation 屬性,否則事務可能會回滾失敗;
- 被 @Transactional 注解的方法所在的類必須被 Spring 管理,否則不生效;
- 底層使用的數據庫必須支持事務機制,否則不生效;
- …