【譯】Spring中@Transcational註解的事務隔離級別與傳播行爲

訪問原文java

前言

在本教程中,咱們將學習@Transactional註解的事務隔離級別和傳播行爲。git

@Transcational註解是什麼?

咱們可使用@Transactional註解爲一個方法添加事務支持。github

咱們能夠經過它指定設置事務的傳播行爲、超時時間與回滾條件。此外,咱們還能夠經過它指定一個事務管理器。spring

Spring經過建立代理對象或者操縱字節碼來實現事務的建立、提交與回滾。sql

在經過代理模式實現事務管理的狀況下,對於來自類內部的調用@Transactional會失效。數據庫

簡單地說,假如咱們有一個叫callMethod的方法並且該方法被@Transactional註解標記過。併發

Spring將使用一些用於事務管理的代碼將這個方法進行包裝,@Transactional註解的實現可使用下面的僞代碼表示:ide

// 若有必要則建立事務
createTransactionIfNecessary();
try {
    // 調用callMethod方法
    callMethod();
    // 調用成功則提交事務
    commitTransactionAfterReturning();
} catch (exception) {
    // 調用失敗則回滾事務
    completeTransactionAfterThrowing();
    throw exception;
}
複製代碼

怎樣使用@Transcational註解

咱們能夠在接口、類、方法上面使用這個註解,位於接口、類、方法上的@Transactional註解會按照不一樣的優先級發生覆蓋,優先級底的將被高的覆蓋掉,覆蓋的優先級從低到高分別是:性能

  • 接口
  • 超類
  • 接口方法
  • 超類方法
  • 類方法

Spring會把一個類上標註的@Transactional註解應用到該類全部public的方法上,因此咱們不須要再單獨給方法上標註@Transactional學習

可是,若是咱們在privateprotected方法上標註@Transcational註解,那麼Spring將會忽略這些註解並且不給出任何錯誤提示。

讓咱們從在接口上標註@Transcational註解開始:

@Transactional
public interface TransferService {
    void transfer(String user1, String user2, double val);
}
複製代碼

一般狀況下,不推薦在接口上標註@Transactional註解,然而在某些接口上使用也是能夠接受的,好比:Spring Data的@Repository接口。

咱們能夠將這個註解標註在類的定義上,這樣能夠覆蓋接口或超類中標註的@Transactional註解:

@Service
@Transactional
public class TransferServiceImpl implements TransferService {
    @Override
    public void transfer(String user1, String user2, double val) {
        // ...
    }
}
複製代碼

如今,讓咱們將這個註解直接標註在方法的定義上:

@Transactional
public void transfer(String user1, String user2, double val) {
    // ...
}
複製代碼

事務傳播行爲

事務傳播行爲定義了咱們業務邏輯的事務邊界,Spring根據咱們配置的事務傳播行爲開始或者中斷事務。

Spring經過調用TransactionManager::getTransaction方法而後根據事務的傳播行爲來獲取或者建立一個事務,它支持部分在TransactionManager中定義的事務傳播行爲,可是還有一些特殊的事務傳播行爲須要經過一些特殊的 TransactionManager實現去支持。

REQUIRED

REQUIRED是默認的事務傳播行爲。對於該傳播行爲而言,Spring會檢查當前線程上下文中是否存在一個活躍的事務。

若是不存在活躍的事務則會建立一個新的事務。

若是存在一個活躍的事務則將當前的業務邏輯算入這個活躍的事務範圍中:

@Transactional(propagation = Propagation.REQUIRED)
public void requiredExample(String user) { 
    // ... 
}
複製代碼

因爲REQUIRED是默認的事務傳播行爲,因此上面的代碼能夠簡寫成:

@Transactional
public void requiredExample(String user) { 
    // ... 
}
複製代碼

來咱們看一下建立具備REQUIRED傳播行爲事務的僞代碼:

// 檢測是否已經位於事務中
 if (isExistingTransaction()) {
    if (isValidateExistingTransaction()) {
        validateExisitingAndThrowExceptionIfNotValid();
    }
    // 返回已有的事務
    return existing;
}
// 不然不使用事務
return createNewTransaction();
複製代碼

SUPPORTS

對於SUPPORTS而言,Spring會先檢查線程上下文中是否存在一個活躍的事務,若是存在一個活躍的事務則使用它。若是沒有,則以不使用事務的方式去執行supportsExample()方法::

@Transactional(propagation = Propagation.SUPPORTS)
public void supportsExample(String user) { 
    // ... 
}
複製代碼

讓咱們看看使用SUPPORTS傳播行爲建立事務的僞代碼:

// 檢測是否已經位於事務中
if (isExistingTransaction()) {
    if (isValidateExistingTransaction()) {
        validateExisitingAndThrowExceptionIfNotValid();
    }
    // 返回已有的事務
    return existing;
}
// 不然不使用事務
return emptyTransaction;
複製代碼

MANDATORY

當傳播行爲被設爲MANDATORY時,若是Spring沒有在線程上下文中找到一個活躍的事務則會拋出一個異常:

@Transactional(propagation = Propagation.MANDATORY)
public void mandatoryExample(String user) { 
    // ... 
}
複製代碼

讓咱們看一下該選項對應的僞代碼表示:

// 檢測是否已經位於事務中
if (isExistingTransaction()) 
{
    if (isValidateExistingTransaction()) {
        validateExisitingAndThrowExceptionIfNotValid();
    }
    // 返回已有的事務
    return existing;
}
// 不然拋出異常
throw IllegalTransactionStateException;
複製代碼

NEVER

當傳播行爲被設爲NEVER時,Spring在線程上下文中發現一個活躍事務時會拋出一個異常:

@Transactional(propagation = Propagation.NEVER)
public void neverExample(String user) { 
    // ... 
}
複製代碼

讓咱們看看使用NEVER傳播行爲建立事務的僞代碼:

// 檢測是否已經位於事務中
if (isExistingTransaction()) {
    // 若是位於事務中就拋出異常
    throw IllegalTransactionStateException;
}
// 不然不使用事務
return emptyTransaction;
複製代碼

NOT_SUPPORTED

當傳播行爲被設爲NOT_SUPPORTED時,Spring會將線程上下文中現有的事務進行掛起,而後再以不使用事務的方式去執行notSupportedExample()方法:

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void notSupportedExample(String user) { 
    // ... 
}
複製代碼

JTATransactionManager事務管理器以開箱即用的方式支持真正的事務掛起。其它 的事務管理器則是經過保存現有事務的引用而後將其從線程上下文中清除的方法來模擬事務掛起。

REQUIRES_NEW

當傳播行爲設爲REQUIRES_NEW時,Spring發現線程上下文中存在一個活躍的事務時,則先將其掛起,而後建立一個新的事務去執行requiresNewExample()方法:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNewExample(String user) { 
    // ... 
}
複製代碼

NOT_SUPPORTED選項相似,對於真正的事務掛起,咱們須要使用JTATransactionManager事務管理器

該選項對應的僞代碼實現:

// 檢測是否已經位於事務中
if (isExistingTransaction()) {
    // 先掛起已存在的事務
    suspend(existing);
    try {
        // 而後建立一個新事務
        return createNewTransaction();
    } catch (exception) {
        // 若是新事務發生異常則恢復舊事務
        resumeAfterBeginException();
        throw exception;
    }
}
// 沒有找到舊事務,直接建立新事務
return createNewTransaction();
複製代碼

NESTED

對於NESTED而言,Spring會檢測線程上下文中是否存在活躍的事務。

若是已經存在則在該事務上創建一個保存點,這意味着若是咱們的nestedExample()方法發生異常,該事務將會回滾到咱們創建的保存點處。

若是沒有檢測到活躍事務的存在,那麼該選項的行爲與REQUIRED選項同樣。

DataSourceTransactionManager支持以開箱即用的方式使用該選項。此外,JTATransactionManager的某些實現也支持這個選項。

JpaTransactionManager僅對JDBC鏈接支持NESTED選項。可是,若是咱們將nestedTransactionAllowed標誌設置爲true,並且咱們的JDBC驅動程序也支持保存點功能,則它也適用於在JPA事務中的JDBC訪問代碼。

最後,讓咱們把傳播行爲設爲NESTED

@Transactional(propagation = Propagation.NESTED)
public void nestedExample(String user) { 
    // ... 
}
複製代碼

事務隔離級別

隔離性是ACID屬性中的一個:

  • 原子性(Atomicity)
  • 一致性(Consistency)
  • 隔離性(Isolation)
  • 持久性(Durability)

隔離性表示的是數據在併發事務間的可見性。

每種級別的隔離能夠防止在併發事務中零或多個併發反作用的發生:

  • 髒讀:能夠讀取到併發事務還未提交的更改。
  • 不可重複讀:在併發事務中,某個事務對某行數據更改後,其它事務對該行的屢次讀取可能會獲取到不一樣的結果。
  • 幻讀:在併發事務中,若是一個事務添加或刪除了一些行並提交,其它事務在從新執行範圍查詢後可能得到不一樣的結果。

咱們能夠經過@Transactional::isolation設置事務的隔離級別。在Spring中,它可使用下面五個枚舉值:

  • DEFAULT
  • READ_UNCOMMITTED
  • READ_COMMITTED
  • REPEATABLE_READ
  • SERIALIZABLE

Spring中的隔離級別管理

在Spring中事務的默認隔離級別是DEFAULT。因此,當Spring建立新事務時,隔離級別是咱們數據庫中所設置的隔離級別。所以,若是咱們在對數據庫中的數據進行修改時須要注意這一點。

咱們還要思考在調用鏈中的,當各個方法對應不一樣的事務隔離級別時會出現什麼狀況。

一般狀況下,在建立事務時也會設置事務的隔離級別,若是咱們不想讓咱們的調用鏈中的方法存在不一樣的事務隔離級別,咱們能夠設置TransactionManager::setValidateExistingTransactiontrue,它的功能使用僞代碼表示爲:

// 若是隔離級別不是DEFAULT
if (isolationLevel != ISOLATION_DEFAULT) {
    // 當前事務設置的隔離級別與已存在事務的隔離級別不一致則拋出異常
    if (currentTransactionIsolationLevel() != isolationLevel) {
        throw IllegalTransactionStateException
    }
}
複製代碼

如今讓咱們深刻了解不一樣的隔離級別與其做用。

READ_UNCOMMITTED

READ_UNCOMMITTED是隔離性最低的級別,具備最高的併發訪問性能,該級別下會發生髒讀、不可重複讀與幻讀的狀況。

咱們能夠爲方法或類設置這個隔離級別:

@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void log(String message) {
    // ...
}
複製代碼

Postgres不支持READ_UNCOMMITTED隔離級別,它使用READ_COMMITED隔離級別做爲替代。Oracle不支持READ_UNCOMMITTED級別。

READ_COMMITTED

隔離性的第二個級別是READ_COMMITTED。它能夠防止髒讀的發生。

這個隔離級別下仍是存在不可重複讀與幻讀的狀況,因此在併發事務中未提交的更改對咱們沒有影響,可是,在事務提交後,對一樣數據的從新查詢可能會發生不一樣的結果。

下面的代碼設置事務的隔離級別爲READ_COMMITTED

@Transactional(isolation = Isolation.READ_COMMITTED)
public void log(String message){
    // ...
}
複製代碼

REPEATABLE_READ

隔離性的第三個級別是REPEATABLE_READ。它能夠防止髒讀、不可重複讀的發生。所以,咱們不受併發事務中未提交更改的影響。

一樣,當咱們從新查詢一行數據時,咱們不會獲得不一樣的結果。可是在從新執行範圍查詢時,咱們可能會得到新添加或刪除的行。

此外,它是知足防止更新丟失的要求最低級別,當兩個或多個併發事務讀取並更新同一行時,就會發生更新丟失。REPEATABLE_READ不容許併發事務同時訪問同一行數據。所以,不會出現更新丟失的狀況。

下面的代碼設置事務的隔離級別爲READ_COMMITTED

@Transactional(isolation = Isolation.REPEATABLE_READ) 
public void log(String message){
    // ...
}
複製代碼

REPEATABLE_READ是Mysql中的默認隔離級別。Oracle不支持REPEATABLE_READ隔離級別。

SERIALIZABLE

SERIALIZABLE是隔離等級最高的級別。它能夠防止髒讀、不可重複讀與幻讀。

由於它把併發的事務變成了串行的,因此它可能會致使最低的併發訪問性能,

換句話說,並行執行一組事務的結果與串行執行它們的結果相同。

如今看一下如何把隔離級別設爲SERIALIZABLE

@Transactional(isolation = Isolation.SERIALIZABLE)
public void log(String message){
    // ...
}
複製代碼

總結

在本教程中,咱們詳細探討了@Transaction的傳播行爲。而後,咱們學習了併發事務的反作用與隔離級別。

與往常同樣,你能夠在GitHub上找到完整的代碼。

相關文章
相關標籤/搜索