什麼是事務?
事務是邏輯上的一組操作,要麼都執行,要麼都不執行。事務能否生效數據庫引擎是否支持事務是關鍵。比如常用的MySQL數據庫默認使用支持事務的innodb引擎。但是如果把數據庫引擎變爲myisam,那麼程序就不再支持事務了。
事務的特性ACID
- 原子性(Atomicity):一個事務中所有操作要麼全部完成,要麼全部不完成。不會結束在中間某個狀態。如果執行過程中發生錯誤,那麼會回滾到事務開始之前的狀態,就像是這個事務從來沒有執行過一樣。
- 一致性(Consistency):在事務開始之前和結束之後,數據庫的完整性沒有被破壞。
- 隔離性(Isolation):數據庫允許多個併發事務同時對數據進行讀寫和修改。隔離性可以防止多個事務併發執行時由於交叉執行導致數據的不一致。事務的隔離級別分爲不同級別:包括未提交讀,提交讀,可重複讀和串行化。
- 持久性(Durability):事務處理結束後,對數據的修改是永久的。即使系統故障也不會丟失。
Spring對事務的支持
MySQL中保證事務原子性是對已經執行的操作進行回滾。
通過回滾日誌實現的。
所有事務進行的修改都會先記錄到這個回滾日誌中。然後再執行相應的操作。如果執行過程中發生異常,我們利用回滾日誌中的信息將數據回滾到修改之前的樣子。
回滾日誌會先於數據持久化到磁盤上,這樣就保證了數據庫突然宕機,當用戶再次啓動數據庫的時候,還能通過查詢回滾日誌來回滾之前未完成的事務。
Spring支持兩種方式的事務管理
編程式事務管理
其實我們可以理解成手動提交事務和回滾事務。雖然很少使用但是我們可以瞭解一下。有兩種調用方式:TransactionTemplate或者TransactionManager手動管理事務。使用demo如下:
@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected 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);
}
}
其實我個人感覺這麼些雖然很麻煩,但是靈活多了。而另一種常用的方式是聲明式事務管理。雖然使用更加簡單,但是其實除了問題挺不容易排查的。
聲明式事務管理
這種方式代碼入侵性最小。實際上是通過AOP實現的。下面是使用demo:
@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {
//do something
B b = new B();
C c = new C();
b.bMethod();
c.cMethod();
}
Spring事務管理接口介紹
Spring框架中,事務管理相關最重要的接口有三個:
- PlatformTransactionManager:事務管理器,Spring事務策略的核心。
- TransactionDefinition:事務定義信息(事務隔離級別,傳播行爲,超時。只讀,回滾規則)
-
TransactionStatus:事務運行狀態。
我們可以把PlatformTransactionManager 接口看作是事務上層的管理者。而TransactionDefinition和TransactionStatus這兩個接口看作是事務的描述。
PlatformTransactionManager 會根據TransactionDefinition的定義來進行事務管理。TransactionStatus接口提供了一些方法來獲取事務相應的狀態。比如是否是新事務,是否可以回滾等。
PlatformTransactionManager:事務管理接口
Spring並不直接管理事務,而是提供了多種事務管理器。這個管理器的接口就是PlatformTransactionManager。
通過這個接口,Spring爲各個平臺如JDBC,Hibernate,JPA等提供了對應的事務管理器。但是具體的實現就是各個平臺自己的事情了。
TransactionDefinition:事務屬性
事務管理器接口通過getTransaction方法來得到一個事務。這個方法裏的參數是
TransactionDefinition類。這個類就是定義了一些基本的事務屬性。
事務屬性包含五個方面:
- 隔離級別
- 傳播行爲
- 回滾規則
- 是否只讀
- 事務超時
TransactionStatus:事務狀態
TransactionStatus接口用來記錄事務的狀態,該接口定義了一組方法,用來獲取或者判斷事務相應的狀態信息。內容如下:
public interface TransactionStatus{
boolean isNewTransaction(); // 是否是新的事務
boolean hasSavepoint(); // 是否有恢復點
void setRollbackOnly(); // 設置爲只回滾
boolean isRollbackOnly(); // 是否爲只回滾
boolean isCompleted; // 是否已完成
}
事務屬性詳解
實際開發中大家一般都使用@Transactional註解來開啓事務。這個註解裏有一些參數,下面是介紹。
事務傳播行爲
事務傳播行爲是爲了解決業務層方法之間互相調用的事務問題。
當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。例如可以繼續在現有事務中運行,也可以開啓一個新事務,在自己的事務中運行。
舉個例子:我們在A類的a方法中調用了B類的b方法,這個時候如果b方法異常,我們要a也回滾麼?如何讓a回滾?這就是事務傳播行爲的知識了。
在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也定義了枚舉類:
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修飾的內部方法和外部方法屬於同一事務,只要一個方法回滾,整個事務回滾。
TransactionDefinition.PROPAGATION_REQUIRES_NEW
創建一個新事務,如果當前存在事務,則把當前事務掛起。也就是說不管外部方法是否開啓事務,PROPAGATION_REQUIRES_NEW修飾的內部方法都會開啓自己的事務。且開啓的事務相互獨立,互不干擾。舉個例子,如圖的兩個方法。如果a發生異常回滾,b不會回滾。但是如果b發生異常回滾並且把異常拋出來了,a檢測到異常也會回滾。
@Service
Class A {
@Autowired
B b;
@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {
//do something
b.bMethod();
}
}
@Service
Class B {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void bMethod {
//do something
}
}
-
TransactionDefinition.PROPAGATION_NESTED
如果當前存在事務,就在嵌套事務內執行。如果當前沒有事務,就開啓一個事務。也就是說:- 在外部方法開啓事務的情況下,在內部開啓一個新的事務。作爲嵌套事務存在。
- 如果外部方法沒有事務,則單獨開啓一個事務。
簡單來說嵌套事務就是內層事務回滾外層也會回滾。上面的PROPAGATION_REQUIRES_NEW是要把異常拋給a,讓a捕獲才能回滾,否則不回滾的。而嵌套事務只要內層回滾外層都會回滾,這就是區別。
TransactionDefinition.PROPAGATION_MANDATORY
這個是當前存在事務則加入。不存在事務則拋異常。這個使用的很少。TransactionDefinition.PROPAGATION_SUPPORTS
當前存在事務則加入該事務,當前不存在事務就以非事務的方式運行TransactionDefinition.PROPAGATION_NOT_SUPPORTED
以非事務方式運行,如果當前存在事務則掛起TransactionDefinition.PROPAGATION_NEVER
以非事務方式運行,如果當前存在事務則報錯
事務隔離級別
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;
......
}
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 隔離級別 - TransactionDefinition.ISOLATION_READ_UNCOMMITTED
最低的隔離級別,使用這個隔離級別很少,因爲它允許讀取尚未提交的數據變更。可能會導致髒讀,幻讀或者不可重複讀。 - TransactionDefinition.ISOLATION_READ_COMMITTED
允許讀取併發事務已經提交的數據。可以阻止髒讀,但是幻讀和不可重複讀仍然有可能發生 - TransactionDefinition.ISOLATION_REPEATABLE_READ
對同一字段的多次讀取結果是一致的。除非數據是被本身事務所修改。可以阻止髒讀和不可重複讀。但是可能出現幻讀。 - TransactionDefinition.ISOLATION_SERIALIZABLE
最高隔離級別,完全服從ACID。所有的事務逐個執行。事務之間不存在干擾,但是嚴重影響性能,一般也不會使用該級別。
事務超時屬性
指一個事務允許執行的最長時間。如果超過該時間限制事務還沒完成,則自動回滾。在TransactionDefinition 中int值表示時間,單位是秒。默認是-1.表示該事務沒有超時時間。
事務只讀屬性
對於只有查詢功能的事務,可以指定類型爲readonly。即只讀事務。只讀事務不涉及到數據的修改,數據庫會有一些優化手段。適合用在有多條數據庫查詢操作的方法中。只讀爲什麼還要事務呢?
打個比方,現在一個查詢功能是同時查詢彙總和每條詳細數據的。如果我們先查詢了彙總,總金額是100.然後查詢詳細數據,在彙總之後,查詢詳細之前多了一筆金額20.可能查出來的詳細數據就變成120.這樣彙總和詳細數據是不一致的。
而當我們啓動了只讀事務。起碼可以保證我們讀的彙總和詳情是一致的。
事務回滾規則
默認情況下事務只有遇到運行期一場或者Error纔會導致事務回滾。但是在遇到檢查型異常時不會回滾。如果想要回滾特定的異常類型的話,可以指定rollbackFor屬性。
@Transactional註解使用詳解
@Transactional的作用範圍
- 方法: 推薦將註解使用在方法上,不過需要注意的是:該註解只能使用在public方法上,否則不生效
- 類:如果這個註解使用在類上的話,該類的素有public方法都生效
- 接口:不推薦在接口上使用
@Transactional的常用配置參數
- propagation:事務的傳播行爲,默認值是REQUIRED.可選的值上面說過了
- isolation:事務的隔離級別,默認是default,可選的值上面說過了
- timeout:事務的超時時間,默認值-1.就是沒有超時限制。
- readOnly:事務是否只讀,默認是false
- rollbackFor:指定能夠觸發事務回滾的異常類型。可以指定多個異常類型。
@Transactional事務註解原理
上面簡單說過這個註解的實現是基於AOP。而AOP又是使用動態代理實現的。如果目標對象實現了接口,默認情況下會採用JDK的動態代理,如果目標對象沒有實現接口,會使用CGLIB動態代理。
如果一個類或者一個類中的public方法被標註@Transactional註解的話,spring容器就會在啓動的時候爲其創建一個代理類。在調用被@Transactional註解的public方法時,實際上調用的是TransactionInterceptor類中的Invoke方法。這個方法是作用就是在目標方法之前開啓事務,方法執行過程中如果遇到異常的時候回滾事務,方法調用完成之後提交事務。
Spring AOP自調用問題
若同一類中的其它沒有@Transactional註解的方法內部調用有@Transactional註解的方法,有@Transactional註解的方法的事務會失效。這是由於Spring AOP代理的原因造成的。因爲只有當@Transactional註解的方法在類以外被調用的時候,Spring事務管理才生效。
@Transactional的使用注意事項總結
- @Transactional註解只作用到public方法上事務纔會生效,不推薦使用在接口上。
- 避免同一個類中調用@Transactional註解的方法,這樣會導致事務失效。
- 正確的設置@Transactional的rollbackFor和propagation屬性,否則事務可能回滾失敗。
- 被@Transactional註解的方法所在的類必須被spring管理,否則不生效。
- 底層使用的數據庫必須支持事務機制,否則不生效。
本篇筆記就整理到這裏,如果稍微幫到你了記得點個喜歡點個關注,也祝大家工作順順利利~!