天天看點

Spring Data之@Transactional事務傳播和隔離級别

Spring Data之@Transactional事務傳播和隔離級别

概述

簡單地說,如果我們有一個callMethod方法,并将其标記為@Transactional,Spring建立代理或操縱類位元組代碼,以管理事務的建立、送出和復原,僞代碼如下:

createTransactionIfNecessary();
try {
    callMethod();
    commitTransactionAfterReturning();
} catch (exception) {
    completeTransactionAfterThrowing();
    throw exception;
}           

事務傳播(Propagation)

傳播定義了業務邏輯的事務邊界。Spring根據事務傳播的設定來啟動和暫停事務。Spring調用TransactionManager::getTransaction根據傳播屬性擷取或建立事務。

  • REQUIRED
@Transactional(propagation = Propagation.REQUIRED)
public void requiredExample(String user) { 
    // ... 
}           

REQUIRED是預設傳播。Spring檢查是否存在活動事務,如果不存在,則建立一個新事務。否則,業務邏輯将附加到目前活動事務。僞代碼:

if (isExistingTransaction()) {
    if (isValidateExistingTransaction()) {
        validateExisitingAndThrowExceptionIfNotValid();
    }
    return existing;
}
return createNewTransaction();           
  • SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
public void supportsExample(String user) { 
    // ... 
}           

對于SUPPORTS,Spring首先檢查是否存在活動事務。如果存在事務,則将使用現有事務。如果沒有事務,則執行非事務。僞代碼:

if (isExistingTransaction()) {
    if (isValidateExistingTransaction()) {
        validateExisitingAndThrowExceptionIfNotValid();
    }
    return existing;
}
return emptyTransaction;           
  • MANDATORY
@Transactional(propagation = Propagation.MANDATORY)
public void mandatoryExample(String user) { 
    // ... 
}           

當傳播為MANDATORY時,如果存在活動事務,則将使用它。如果沒有活動事務,則Spring抛出異常。僞代碼:

if (isExistingTransaction()) {
    if (isValidateExistingTransaction()) {
        validateExisitingAndThrowExceptionIfNotValid();
    }
    return existing;
}
throw IllegalTransactionStateException;           
  • NEVER
@Transactional(propagation = Propagation.NEVER)
public void neverExample(String user) { 
    // ... 
}           

對于NEVER傳播的事務邏輯,如果存在活動事務,Spring将抛出異常。僞代碼:

if (isExistingTransaction()) {
    throw IllegalTransactionStateException;
}
return emptyTransaction;           
  • NOT_SUPPORTED
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void notSupportedExample(String user) { 
    // ... 
}           

如果目前事務存在,則Spring先将其挂起,然後在沒有事務的情況下執行業務邏輯。

  • REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNewExample(String user) { 
    // ... 
}           

當傳播為REQUIRES_NEW時,Spring将挂起目前事務(如果存在),然後建立一個新事務。僞代碼:

if (isExistingTransaction()) {
    suspend(existing);
    try {
        return createNewTransaction();
    } catch (exception) {
        resumeAfterBeginException();
        throw exception;
    }
}
return createNewTransaction();           
  • NESTED
@Transactional(propagation = Propagation.NESTED)
public void nestedExample(String user) { 
    // ... 
}           

對于NESTED傳播,Spring檢查事務是否存在,如果存在,則标記儲存點。這意味着如果業務邏輯執行抛出異常,那麼事務将復原到此儲存點。如果沒有活動事務,則工作方式與REQUIRED類似。

事務隔離

事務隔離是ACID屬性之一:原子性、一緻性、隔離性和持久性。隔離描述了并發事務所應用的更改如何彼此可見。

每個隔離級别都可以防止對事務産生零或多個并發副作用。

  • 髒讀:讀取并發事務的未送出更改
  • 不可重複讀取:如果并發事務更新同一行并送出,則在重新讀取行時獲得不同的值
  • 幻讀:如果另一個事務添加或删除範圍中的某些行并送出,則在重新執行範圍查詢後擷取不同的行

@Transactional::isolation設定事務的隔離級别有以下五個枚舉:DEFAULT、READ_UNCOMMITTED、READ_COMMITED、REPEATABLE_READ、SERIALIZABLE。

  • Spring隔離管理

預設隔離級别為DEFAULT。是以,當Spring建立新事務時,隔離級别将是RDBMS的預設隔離級别。是以,如果此時更改資料庫,需要小心。

當調用具有不同隔離度的方法鍊時,也應該考慮這些情況。在正常流程中,隔離僅在建立新事務時适用。是以,如果出于任何原因,我們不想讓一個方法以不同的隔離方式執行,必須将TransactionManager::setValidateExistingTransaction設定為true。

事務驗證僞代碼如下:

if (isolationLevel != ISOLATION_DEFAULT) {
    if (currentTransactionIsolationLevel() != isolationLevel) {
        throw IllegalTransactionStateException
    }
}           
  • READ_UNCOMMITED
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void log(String message) {
    // ...
}           

READ_UNCOMMITTED是最低的隔離級别,允許最多的并發通路。

是以,它受到了上述三種并發副作用的影響。具有此隔離的事務讀取其他并發事務的未送出資料。此外,不可重複讀和幻讀都可能發生,在重新讀取行或重新執行範圍查詢時可能獲得不同的結果。

Postgres不支援READ_UNCOMMITTED隔離,而是傳回到READ_COMMITED。此外,Oracle不支援或不允許READ_UNCOMMITTED。

  • READ_COMMITED
@Transactional(isolation = Isolation.READ_COMMITTED)
public void log(String message){
    // ...
}           

第二級别隔離READ_COMMITTED防止髒讀。其他并發副作用仍然可能發生。

是以,并發事務中未送出的更改對我們沒有影響,但如果事務送出了其更改,結果可能會通過重新查詢而改變。

READ_COMMITED是Postgres、SQL Server和Oracle的預設級别。

  • REPEATABLE_READ
@Transactional(isolation = Isolation.REPEATABLE_READ) 
public void log(String message){
    // ...
}           

第三級别隔離REPEATABLE_READ可防止髒讀和不可重複讀。

是以,我們不受并發事務中未送出的更改的影響。當我們重新查詢一行時,我們不會得到不同的結果。然而,在重新執行範圍查詢時,我們可能會得到新添加或删除的行。

此外,這是防止丢失更新所需的最低級别。當兩個或多個并發事務讀取并更新同一行時,會發生丢失的更新。REPEATABLE_READ根本不允許同時通路一行。是以,丢失的更新不會發生。

REPEATABLE_READ是Mysql中的預設級别。Oracle不支援REPEATABLE_READ。

  • SERIALIZABLE
@Transactional(isolation = Isolation.SERIALIZABLE)
public void log(String message){
    // ...
}           

SERIALIZABLE是最進階别的隔離。它可以防止所有提到的并發副作用,但可以導緻最低的并發通路率,因為是按順序執行并發調用。

結論

本文詳細探讨了@Transaction的傳播特性,并發副作用和隔離級别。一般情況下使用預設事務屬性即可,具體配置需要從實際的業務場景涉及的并發事務場景去考慮。