文章目錄
-
- 1 Spring中七種事務傳播行為
- 2 定義
- 3 定理
- 4 推理
- 5 證明
-
- 5.1 事務傳播行為證明
- 5.2 推理證明
- 6 事務切面設定的幾個核心狀态
-
- 6.1 newTransaction
- 6.2 actualNewSynchronization
- 6.3 isSynchronizationActive/actualTransactionActive
- 6.4 connectionHolder
- 6.5 currentTransactionStatus
- 7 required/requires_new/nested 淺析
-
- 7.1 doBegin
- 7.2 prepareSynchronization
- 8 support/not_support/never 淺析
- 9 REQUIRED/SUPPORTS/ MANDATORY 淺析
- 10 NESTED 淺析
- 11 REQUIRES_NEW 淺析
- 12 NOT_SUPPORTED 淺析
- 13 資料庫操作執行時用到和設定的幾個狀态
-
- 13.1 擷取 session
- 13.2 擷取 connection
- 14 connection auto-commit 屬性介紹
- 15 善用 PROPAGATION_SUPPORTS
- 參考
- Spring 在 TransactionDefinition 接口中規定了 7 種類型的事務傳播行為
- 事務傳播行為是Spring架構獨有的事務增強特性,不屬于資料庫行為
- 通過這 7 種 傳播行為,可以使我們更靈活的控制哪些行為在同一個事務内、哪些行為不在同一事務内、各事務之間的關系,進而使得我們的業務邏輯更加靈活
1 Spring中七種事務傳播行為
- 事務傳播行為規定了在一個嵌套方法内部,哪些方法屬于同一個事務,哪些方法不屬于同一個事務,以及事務之間的關系
事務傳播行為類型 | 說明 |
---|---|
PROPAGATION_REQUIRED | 如果存在則加入事務。如果不存在,建立新事務 |
PROPAGATION_SUPPORTS | 如果存在則加入事務。如果不存在,以不可用事務執行 |
PROPAGATION_MANDATORY | 如果存在則加入事務。如果不存在,抛出異常 |
PROPAGATION_REQUIRES_NEW | 如果存在則挂起目前事務,并建立新事務。如果不存在則建立事務 |
PROPAGATION_NESTED | 如果存在則建立嵌套事務。如果不存在,建立新事務 |
PROPAGATION_NOT_SUPPORTED | 以不可用事務運作。如果存在事務,則先挂起目前事務 |
PROPAGATION_NEVER | 以不可用事務運作。如果存在事務,抛出異常 |
2 定義
- null:表示沒有 @Transactional 注解的方法,且不在事務内
- NULL:表示沒有 @Transactional 注解的方法,但在事務内(其外部有事務,是以加入外部事務)
- required:表示有 @Transactional(propagation = Propagation.REQUIRED) 的方法,但是外部沒有事務
- REQUIRED:表示有 @Transactional(propagation = Propagation.REQUIRED) 的方法,但是外部有事務
- 同理其它 6 個傳播行為
- 資料庫錯誤:表示有 @Transactional 注解的方法,執行資料庫操作時失敗,但是異常最終被 catch 掉了
- 異常:方法抛出來的、沒有被捕獲的異常
- 方法被復原:表示方法内部的資料庫操作被復原
- 事務切面方法:表示加入事務,且加入事務切面(被 @Transactional 注解注釋的方法才會加入事務切面)
- 事務方法:表示加入事務的方法(包含事務切面方法)
- 外層事務:表示先加入同一事務的方法
- 真實事務:表示有 @Transactional 注解的方法
- 普通方法:沒有 @Transactional 注解的方法
3 定理
- null 方法不受事務影響,隻要執行成功就不會被復原(例:插入了一行資料後,執行其它語句立馬報錯也不會被復原,因為null 方法插入後會立馬送出,其 auto-commit 為 true)
- 事務内發生 異常,加入這個事務的所有資料庫操作都會被復原
4 推理
- NULL 方法受事務影響,外層事務被復原,NULL 方法也會被復原(…方法證明)
- 如果 NULL 方法發生 資料庫錯誤,不影響事務(即是 3)
- required 内部的 NULL 方法發生 資料庫錯誤,required 方法不會被復原(雖然在同一事務内,但是 NULL 方法的資料庫異常不會傳遞到事務切面,因為 NULL 方法雖然加入了事務,但是沒有加入事務切面)
- required 内部的 REQUIRED 方法發生 資料庫錯誤,required 方法被復原(通過事務切面将復原狀态傳遞到外層)
- required 内部的 SUPPORTS 方法發生 資料庫錯誤,required 方法被復原(同理)
- required 内部的 MANDATORY 方法發生 資料庫錯誤,required 方法被復原(同理)
- required 内部的 REQUIRES_NEW 方法發生 資料庫錯誤,required 方法不會被復原(不在同一個事務)
- required 内部的 NESTED 方法發生 資料庫錯誤,required 方法不會被復原(NESTED 發生資料庫錯誤會復原到儲存點,保證了外部事務的有效性)
- required 内部的 NOT_SUPPORTED 方法發生 資料庫錯誤,required 方法不會被復原(NOT_SUPPORTED 方法 不在事務内)
- requires_new、nested 情況和 required 相同,因為這兩個都隻是開啟新事務,意思和 required 一模一樣
- supports、not_supported、never 都是以非事務運作和 null 效果一樣(null之内的普通方法不能複用session)
- mandatory 直接抛異常
- NESTED:--------0------0---------,兩個0 之間是嵌套事務,不管是内層嵌套事務還是外層事務,其實是一個事務,隻是在這個事務線上建立了一個儲存點,嵌套事務内部事情處理完了之後,整個事務狀态會回到儲存點時的狀态,這樣保證了即使嵌套内部發生資料庫錯誤(這時仍然會設定事務為 rollback 狀态),因為方法完了之後事務狀态會回到儲存點,是以 rollback 狀态被覆寫了
- NESTED 和 NULL 的差別
- NULL 内部的 事務方法 屬于 NULL 所在事務,而 NESTED 内部的 事務方法 屬于 NESTED 開啟的嵌套事務和外層事務沒關系
- NULL 内部的 事務切面方法 發生 資料庫錯誤 會導緻外部事務復原,而 NESTED 内部的事務切面方法 發生 資料庫錯誤 隻會導緻 NESTED 開啟的嵌套事務復原,而外層事務不會
- NESTED 和 REQUIRES_NEW 的差別
- 外層事務發生 資料庫錯誤 會導緻 NESTED 方法復原,而不會導緻 REQUIRES_NEW 方法復原
5 證明
5.1 事務傳播行為證明
- 事務傳播行為證明
5.2 推理證明
- 推理證明
6 事務切面設定的幾個核心狀态
6.1 newTransaction
- 是否是新開啟的事務
- 此屬性是直接指派的,沒有判斷邏輯
- 所有的小寫/REQUIRES_NEW 為 true
- 所有的大寫(除了 REQUIRES_NEW) 為 false
- 事務關閉時如果 newTransaction= true 且 actualTransactionActive=true,才會做 connection 的 commit 操作
6.2 actualNewSynchronization
- 目前線程是否是新開啟同步的
- 如果目前線程已經開啟同步了,傳回 false。否則,傳回 true
AbstractPlatformTransactionManager#newTransactionStatus
actualNewSynchronization = !TransactionSynchronizationManager.isSynchronizationActive()
6.3 isSynchronizationActive/actualTransactionActive
- 目前線程是否同步/目前線程的事務是否可用
- 如果 actualNewSynchronization 為 false 的話
- 如果 status 中的 transaction 為 null,actualTransactionActive=false。否則為 true
- isSynchronizationActive = true
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
if (status.isNewSynchronization()) {
//關鍵 1:設定目前線程事務是否活躍
setActualTransactionActive(status.hasTransaction());
//關鍵 2: 設定事務是否已經同步
//synchronizations.set(new LinkedHashSet<>());
initSynchronization();
}
}
6.4 connectionHolder
- 此屬性隻有在建立新的、可用的事務時,才會設定
- transaction.newConnectionHolder = true
- conHolder.setSynchronizedWithTransaction(true)
- conHolder.setTransactionActive(true)
- connection.setAutoCommit(false)
org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin
在 doBegin 方法中做了以下操作
1. 建立 connectionHolder
2. 将此 connectionHolder 設定到目前事務中,并設定 newConnectionHolder = true
3. 設定 ConnectionHolder().setSynchronizedWithTransaction(true) ----> 1
4. 将此 connectionHolder 中的 connection 設定 auto-commit = false -----> 2
5. 設定 事務的 read only 狀态(如果指定了 read only 的話)
6. getConnectionHolder().setTransactionActive(true) -----> 3
6.5 currentTransactionStatus
- 設定目前線程的事務狀态
TransactionAspectSupport#prepareTransactionInfo#bindToThread
//擷取目前事務的狀态:TransactionStatus[transaction,newTransaction,newSynchronization,suspendedResources,readOnly]
//可以看出 之前很多狀态資訊 都在 TransactionStatus 中儲存了
//這個線程中儲存了 舊的 TransactionStatus 和目前的 TransactionStatus,舊的用于事務挂起等功能
private void bindToThread() {
this.oldTransactionInfo = transactionInfoHolder.get();
transactionInfoHolder.set(this);
}
7 required/requires_new/nested 淺析
- 此三個狀态為 建立新事務
- 事務切面後的核心狀态資訊
- newTransaction=true(是新事務)
- actualNewSynchronization=true(新開啟的)
- isSynchronizationActive=true(新開啟同步)
- actualTransactionActive = true(事務可用)
- 建立并設定 connectionHolder(設定特定的 connectionHolder)
//AbstractPlatformTransactionManager#getTransaction
suspendedResources = suspend(null);//内部不進行任何操作,傳回 null
try {
boolean newSynchronization = 始終為 true;
//status 的關鍵屬性, newTransaction=true,actualNewSynchronization=true
//說明是新事務,且新開啟事務同步(後面加入事務的就是 false)
DefaultTransactionStatus status = newTransactionStatus();
//建立新的connection,并設定其狀态為和事務同步且代表一個活動的事務,并加入到線程中
//設定事務 read only 辨別,如果需要的話
doBegin(transaction, definition);
//設定目前線程的事務狀态:hasTransaction=true, isSynchronizationActive()=true
//這樣在建立 session 後,就會将 session 加入目前線程,重複使用(registerSessionHolder())
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException | Error ex) {
//這裡的 suspendedResources == null,内部不會做任何操作
resume(null, suspendedResources);
throw ex;
}
7.1 doBegin
protected void doBegin(Object transaction, TransactionDefinition definition) {
try {
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
//因為事務中還沒有 connection 是以進入此處擷取 con(這裡就可以動态切換資料源了)
Connection newCon = obtainDataSource().getConnection();
//将此 connection 設定到事務中,并标記為 新的連接配接
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
//标記此 connection 為和事務同步狀态
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
Integer previousIsolationLevel = 事務隔離級别;
//Switch to manual commit
//設定事務 read only 狀态(如果事務标記了 read only,通過注解中的屬性标記)
prepareTransactionalConnection(con, definition);
//标記此 connection 代表一個活動的事務
txObject.getConnectionHolder().setTransactionActive(true);
if (txObject.isNewConnectionHolder()) {
//可以走到這,将上面建立的 connection 設定到目前線程中,這樣統一事務就複用 con 了
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
catch (Throwable ex) {
if (txObject.isNewConnectionHolder()) {
//發生異常則關閉連接配接
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}
7.2 prepareSynchronization
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
if (status.isNewSynchronization()) {//肯定為 true
setActualTransactionActive(status.hasTransaction());//肯定為 true
setCurrentTransactionIsolationLevel(..);
setCurrentTransactionReadOnly(..);
setCurrentTransactionName(..);
//synchronizations.set(new LinkedHashSet<>());
initSynchronization();
}
}
8 support/not_support/never 淺析
- 此三個狀态為以 不可用事務 執行狀态
- 事務切面後的核心狀态資訊
- newTransaction=true(是新事務)
- actualNewSynchronization=true(新開啟的)
- isSynchronizationActive=true(新開啟同步)
- actualTransactionActive = false(事務不可用)
- 沒有建立 conHolder
- 這 3 個方法表示以 非事務執行
- 這樣雖然執行時建立了 session和connection 并加入了線程,當内部 事務方法 執行時也會重新建立事務,并覆寫 session/connection 資訊
- 技巧: 當此3種方法内部有 普通方法 時,普通方法會複用 session/connection,減輕了建立 session/connection 的消耗,特别是在高并發的時候
- 并且沒有執行 doBegin() 方法建立并設定 connection 的 auto-commit 為 false!,使得資料庫操作一執行完就立刻送出了
@Override protected boolean isExistingTransaction(Object transaction) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; //這裡傳回 false,因為 isTransactionActive() = false //傳回 false,就導緻新來的 真實事務 方法會走沒有事務存在的邏輯,這樣就覆寫了非事務綁定的 session 和 connection 資訊了 return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive()); }
- 和 6 的差別僅于
- 6 通過 doBegin() 方法,建立 connection 相關資訊
- 7 中的事務狀态 hasTransaction=false,actualTransactionActive=false
//AbstractPlatformTransactionManager#getTransaction
boolean newSynchronization = 這個目前始終為 true;
//設定目前線程的事務狀态:hasTransaction=true, isSynchronizationActive()=true
//ActualTransactionActive = false
//這樣在建立 session 後,就會将 session 加入目前線程,重複使用(registerSessionHolder())
return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
protected final DefaultTransactionStatus prepareTransactionStatus(
TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,
boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) {
//transaction 為 null,hasTransaction=false, isSynchronizationActive()=true
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, newTransaction, newSynchronization, debug, suspendedResources);
prepareSynchronization(status, definition);
return status;
}
9 REQUIRED/SUPPORTS/ MANDATORY 淺析
- 此三個狀态為 加入已存在事務
- 事務切面後的核心狀态資訊
- newTransaction=false(不是新事務)
- actualNewSynchronization=false(不是新同步的)
- 沒有設定 isSynchronizationActive(沿用建立事務時的狀态,一般 true)
- 沒有設定 actualTransactionActive(沿用建立事務時的狀态,一般 true)
- 沒有建立 conHolder(沿用建立事務時建立 conHolder)
//AbstractPlatformTransactionManager#handleExistingTransaction
boolean newSynchronization = 始終是 true;
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
protected final DefaultTransactionStatus prepareTransactionStatus(
TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,
boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) {
//hasTransaction=true,newTransaction=false,newSynchronization=false
//以上值說明,目前的事務不是新開啟的事務,也不是新同步的事務
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, newTransaction, newSynchronization, debug, suspendedResources);
//因為 newSynchronization=false,此方法内部不做任何處理
prepareSynchronization(status, definition);
return status;
}
10 NESTED 淺析
- 此狀态為 建立嵌套事務。和 9 的唯一差別就是建立了一個 savepoint。在嵌套事務内發生的 資料庫錯誤,會復原到這個儲存點,是以并不會影響外部事務。外部事務仍可影響内部事務
- 事務切面後的核心狀态資訊
- newTransaction=false(不是新事務)
- actualNewSynchronization=false(不是新同步的)
- 沒有設定 isSynchronizationActive(沿用建立事務時的狀态,一般 true)
- 沒有設定 actualTransactionActive(沿用建立事務時的狀态,一般 true)
- 沒有建立 conHolder(沿用建立事務時建立 conHolder)
DefaultTransactionStatus status =
prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
//和 9 的唯一差別就是場景 savepoint
status.createAndHoldSavepoint();
11 REQUIRES_NEW 淺析
- 挂起之前的事務,重新建立新事務。完全可以當作 7 來看,隻是挂起了之前的事務
- 事務切面後的核心狀态資訊
- newTransaction=true(是新事務)
- actualNewSynchronization=true(新開啟的)
- isSynchronizationActive=true(新開啟同步)
- actualTransactionActive = true(事務可用)
- 建立并設定 connectionHolder(設定特定的 connectionHolder)
//挂起之前的事務(把之前的事務資訊儲存到目前事務,目前事務完成後,在設定回去)
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
boolean newSynchronization = true;
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException | Error beginEx) {
//發生異常也重置挂起的事務
resumeAfterBeginException(transaction, suspendedResources, beginEx);
throw beginEx;
}
12 NOT_SUPPORTED 淺析
- 挂起之前事務 (之前的 connection/session 資訊沒有了),以不可用事務執行。和 8 的唯一差別是儲存了之前事務。該事務執行完之後會重新設定之前的事務
- 事務切面後的核心狀态資訊
- newTransaction=true(是新事務)
- actualNewSynchronization=true(新開啟的)
- isSynchronizationActive=true(新開啟同步)
- actualTransactionActive = false(事務不可用)
- 沒有建立 conHolder
Object suspendedResources = suspend(transaction);
boolean newSynchronization = true;
return prepareTransactionStatus(
definition, null, false, newSynchronization, debugEnabled, suspendedResources);
13 資料庫操作執行時用到和設定的幾個狀态
13.1 擷取 session
- session 隻要有任何事務,都會加入到線程中,不可用事務也是
//org.mybatis.spring.SqlSessionUtils#getSqlSession#sessionHolder
//如果目前線程中的 sessionHolder 不為 null 且 此 session 和 事務處于同步狀态,就從事務中得到 session,否則傳回 null
//目前隻要有 holder 則 isSynchronizedWithTransaction 肯定為 true
if (holder != null && holder.isSynchronizedWithTransaction()) {}
//org.mybatis.spring.SqlSessionUtils#getSqlSession#registerSessionHolder
//如果目前線程事務處于同步狀态,則将建立的 session 綁定到目前線程中
//隻要方法或方法外層有 @Transactional 任意類型注解,此處都為 true
if (TransactionSynchronizationManager.isSynchronizationActive()) {}
13.2 擷取 connection
- 在此處建立的 connection 都不具備手動 commit/rollback 能力
- 隻有在 doBegin() 方法中建立并加入事務的才正真具備事務能力
//org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection
//如果目前線程存在 conHolder 且 conHolder 中存在 connection 或者 conholder 已經和事務同步,則直接傳回 conHolder 中的 connection
//隻要 conHolder 不為 null,後面一般都為 true
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
}
//org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection
//隻要方法或方法外層有 @Transactional 任意類型注解,此處一般都為 true
if (TransactionSynchronizationManager.isSynchronizationActive()) {}
14 connection auto-commit 屬性介紹
- 總的來說如果 auto-commit 為 true,則資料庫操作全部完成時就自動送出
将此連接配接的自動送出模式設定為給定狀态。
如果連接配接處于自動送出模式,則其所有SQL語句都将作為單個事務執行和送出。否則,隻會在事務完成或失敗後統一進行 commit 或 rollback。
預設情況下,新連接配接處于自動送出模式。自動送出發生在 statement 完成後。
statement 完成的時間取決于SQL語句的類型:
1. 對于DML語句,如Insert、Update或Delete和DDL語句,該語句在完成執行後即完成。
2. 對于Select語句,當關聯的結果集關閉時,該語句即完成。
3. 對于CallableStatement對象或傳回多個結果的語句,當所有關聯的結果集都已關閉,并且已檢索到所有更新計數和輸出參數時,該語句即完成。
- 總的來說就是資料庫操作全部完成時就自動送出
注意:如果在事務期間調用此方法,并且更改了自動送出模式,則将送出事務。如果調用setAutoCommit并且自動送出模式沒有更改,則調用是no-op。
void setAutoCommit(boolean autoCommit) throws SQLException;
15 善用 PROPAGATION_SUPPORTS
- 如果存在則加入事務。如果不存在,以非事務執行
- 此 傳播類型其實和沒有 @Transactional 标記的作用基本一緻
- 如果外部有事務,則 這兩種情況都加入事務
- 如果外部沒有事務,則這兩種情況都會自動送出
- 差別: SUPPORTS 方法在外圍沒有事務時,也會建立 session/connection 并加入到線程,這樣在此方法内的其它 普通方法 都會重用線程中的 session/connection,避免了頻繁建立!
參考
spring-boot-starter:2.1.13.RELEASE
mybatis-spring-boot-starter:2.1.1