spring 事務(wù)詳解

事務(wù)特性(ACID)

  • 原子性: 事務(wù)是最小的執(zhí)行單位,不允許分割。事務(wù)的原子性確保動(dòng)作要么全部完成,要么完全不起作用
  • 一致性: 執(zhí)行事務(wù)前后,數(shù)據(jù)保持一致
  • 隔離性: 并發(fā)訪問數(shù)據(jù)庫時(shí),一個(gè)用戶的事物不被其他事務(wù)所干擾也就是說多個(gè)事務(wù)并發(fā)執(zhí)行時(shí),一個(gè)事務(wù)的執(zhí)行不應(yīng)影響其他事務(wù)的執(zhí)行
  • 持久性: 一個(gè)事務(wù)被提交之后。它對數(shù)據(jù)庫中數(shù)據(jù)的改變是持久的,即使數(shù)據(jù)庫發(fā)生故障也不應(yīng)該對其有任何影響

spring 事務(wù)管理接口介紹

Spring 框架中,事務(wù)管理相關(guān)最重要的 3 個(gè)接口如下:

  • PlatformTransactionManager: (平臺)事務(wù)管理器,Spring 事務(wù)策略的核心
  • TransactionDefinition: 事務(wù)屬性(事務(wù)隔離級別、傳播行為、超時(shí)、只讀、回滾規(guī)則)
  • TransactionStatus: 事務(wù)運(yùn)行狀態(tài)

我們可以把 PlatformTransactionManager 接口可以被看作是事務(wù)上層的管理者,而 TransactionDefinitionTransactionStatus 這兩個(gè)接口可以看作是事務(wù)的描述。

PlatformTransactionManager 會根據(jù) TransactionDefinition 的定義(比如事務(wù)超時(shí)時(shí)間、隔離界別、傳播行為等)來進(jìn)行事務(wù)管理 ,而 TransactionStatus 接口則提供了一些方法來獲取事務(wù)相應(yīng)的狀態(tài)(比如是否新事務(wù)、是否可以回滾)

一、PlatformTransactionManager(事務(wù)管理)

spring 事務(wù)管理接口,通過這個(gè)接口,Spring 為各個(gè)平臺如 JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了對應(yīng)的事務(wù)管理器,但是具體的實(shí)現(xiàn)就是各個(gè)平臺自己的事情了

PlatformTransactionManager 接口中定義了三個(gè)方法:

public interface PlatformTransactionManager {
    //獲得事務(wù)
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
    //提交事務(wù)
    void commit(TransactionStatus var1) throws TransactionException;
    //回滾事務(wù)
    void rollback(TransactionStatus var1) throws TransactionException;
}

二、TransactionDefinition(事務(wù)屬性)

用于描述事務(wù)隔離級別、傳播行為、超時(shí)、只讀、回滾規(guī)則

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;
    // 返回事務(wù)的傳播行為,默認(rèn)值為 REQUIRED。
    int getPropagationBehavior();
    //返回事務(wù)的隔離級別,默認(rèn)值是 DEFAULT
    int getIsolationLevel();
    // 返回事務(wù)的超時(shí)時(shí)間,默認(rèn)值為-1。如果超過該時(shí)間限制但事務(wù)還沒有完成,則自動(dòng)回滾事務(wù)。
    int getTimeout();
    // 返回是否為只讀事務(wù),默認(rèn)值為 false
    boolean isReadOnly();

    @Nullable
    String getName();
}

三、TransactionStatus(事務(wù)狀態(tài))

TransactionStatus 接口用來記錄事務(wù)的狀態(tài),該接口定義了一組方法用來獲取或判斷事務(wù)的相應(yīng)狀態(tài)信息

PlatformTransactionManager.getTransaction(…) 方法返回一個(gè) TransactionStatus 對象

TransactionStatus 接口接口內(nèi)容如下:

public interface TransactionStatus{
    // 是否是新的事物
    boolean isNewTransaction(); 
    // 是否有恢復(fù)點(diǎn)
    boolean hasSavepoint(); 
    // 設(shè)置為只回滾
    void setRollbackOnly();  
    // 是否為只回滾
    boolean isRollbackOnly(); 
    // 是否已完成
    boolean isCompleted; 
}

spring 事務(wù)屬性

事務(wù)傳播行為

一、簡介

事務(wù)傳播行為(propagation behavior)指的就是當(dāng)一個(gè)事務(wù)方法被另一個(gè)事務(wù)方法調(diào)用時(shí),這個(gè)事務(wù)方法應(yīng)該如何進(jìn)行。

事務(wù)傳播行為是為了解決業(yè)務(wù)層方法之間互相調(diào)用的事務(wù)問題

spring 定義了 7 中事務(wù)傳播行為,其含義如下:

事務(wù)行為 說明
PROPAGATION_REQUIRED 如果存在一個(gè)事務(wù),則支持當(dāng)前事務(wù)。如果沒有事務(wù)則開啟一個(gè)新的事務(wù)
PROPAGATION_SUPPORTS 當(dāng)前方法不需要事務(wù)上下文,但若存在當(dāng)前事務(wù),則該方法會加入當(dāng)前事務(wù)
PROPAGATION_MANDATORY 該方法必須在事務(wù)中運(yùn)行,若當(dāng)前事物不存在,則拋出一個(gè)異常:IllegalTransactionStateException("Transaction propagation ‘mandatory’ but no existing transaction found")
PROPAGATION_REQUIRES_NEW 當(dāng)前方法必須運(yùn)行在它自己的事務(wù)中。一個(gè)新的事務(wù)將被啟動(dòng)。若存在當(dāng)前事務(wù),則該方法執(zhí)行期間,當(dāng)前事務(wù)掛起。若用 JTATransactionManager 的話,則需訪問 TransactionManager。內(nèi)層事務(wù)和外層事務(wù)相互獨(dú)立,互不影響
PROPAGATION_NOT_SUPPORTED 該方法以非事務(wù)方式運(yùn)行。若存在當(dāng)前事務(wù),則在該方法運(yùn)行期間,當(dāng)前事務(wù)掛起。若用 JTATransactionManager 的話,則需訪問 TransactionManager
PROPAGATION_NEVER 該方法不應(yīng)該運(yùn)行在事務(wù)上下文,若當(dāng)前有一個(gè)事務(wù)正在運(yùn)行,則拋出異常
PROPAGATION_NESTED 若當(dāng)前已存在一個(gè)事務(wù),則該方法將會在嵌套事務(wù)中運(yùn)行。若沒有活動(dòng)事務(wù), 則按 PROPAGATION_REQUIRED 執(zhí)行。嵌套事務(wù)一個(gè)非常重要的概念就是內(nèi)層事務(wù)依賴于外層事務(wù)。外層事務(wù)失敗時(shí),會回滾內(nèi)層事務(wù)所做的動(dòng)作。而內(nèi)層事務(wù)操作失敗并不會引起外層事務(wù)的回滾。嵌套事務(wù)開始執(zhí)行時(shí), 它將取得一個(gè) savepoint。若嵌套事務(wù)失敗, 則回滾到此 savepoint。嵌套事務(wù)是外部事務(wù)的一部分, 只有外部事務(wù)結(jié)束后它才會被提交

二、示例設(shè)計(jì)

示例設(shè)計(jì)中,我們主要分為兩部分:同一類中事務(wù)方法的傳播行為以及不同類中事務(wù)方法的傳播行為。我們以表中的情況進(jìn)行組合,模擬事務(wù)方法的傳播行為

function a(外層事務(wù)方法) function b(內(nèi)層事務(wù)方法)
外層事務(wù)正常執(zhí)行,不捕獲內(nèi)層事務(wù)異常 內(nèi)層事務(wù)正常執(zhí)行
外層事務(wù)正常執(zhí)行,捕獲內(nèi)層事務(wù)異常 內(nèi)層事務(wù)執(zhí)行異常,捕獲異常
外層事務(wù)執(zhí)行異常,拋出異常 內(nèi)層事務(wù)執(zhí)行異常,拋出異常
外層事務(wù)執(zhí)行異常,捕獲異常 內(nèi)層非事務(wù)

2.1 同一類中事務(wù)方法的傳播行

2.2 不同類中事務(wù)方法的傳播行為

2.2.1 內(nèi)層方法沒有聲明事務(wù)

場景:外層 PROPAGATION_REQUIRED,內(nèi)層沒有事務(wù)

結(jié)果:正常執(zhí)行

@Transactional(rollbackFor = Exception.class)
public void function() {
    // todo database operate
    serviceB.innerFunction();
}

public void innerFunction() {
    // todo database operate
}

場景:外層 PROPAGATION_REQUIRED,內(nèi)層拋出異常,外層沒有捕獲異常

結(jié)果:全部回滾

@Transactional(rollbackFor = Exception.class)
public void function() {
    // todo database operate
    serviceB.innerFunction();
}

public void innerFunction() {
    // todo database operate
    throw new RuntimeException("innerFunction RuntimeException");
}

場景:外層 PROPAGATION_REQUIRED,內(nèi)層拋出異常,外層捕獲異常

結(jié)果:正常執(zhí)行,不回滾

@Transactional(rollbackFor = Exception.class)
    public void function() {
        // todo database operate
        try {
            serviceB.innerFunction();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

public void innerFunction() {
    // todo database operate
    throw new RuntimeException("innerFunction RuntimeException");
}
2.2.2 PROPAGATION_REQUIRED

使用的最多的一個(gè)事務(wù)傳播行為,我們平時(shí)經(jīng)常使用的 @Transactional 注解默認(rèn)使用就是這個(gè)事務(wù)傳播行為。如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則創(chuàng)建一個(gè)新的事務(wù)。也就是說:

  1. 如果外部方法沒有開啟事務(wù)的話,Propagation.REQUIRED 修飾的內(nèi)部方法會新開啟自己的事務(wù),且開啟的事務(wù)相互獨(dú)立,互不干擾
  2. 如果外部方法開啟事務(wù)并且被 Propagation.REQUIRED 的話,所有 Propagation.REQUIRED 修飾的內(nèi)部方法和外部方法均屬于同一事務(wù) ,只要一個(gè)方法回滾,整個(gè)事務(wù)均回滾

場景:內(nèi)層 PROPAGATION_REQUIRED,并拋出異常,外層事務(wù)不捕獲異常

結(jié)果:全部回滾

原因:外層事務(wù)方法沒有捕獲內(nèi)層事務(wù)方法拋出的異常,因此進(jìn)行回滾操作

@Transactional(rollbackFor = Exception.class)
public void function() {
    // todo database operate
    serviceB.innerFunction();
}

@Transactional(rollbackFor = Exception.class)
public void innerFunction() {
    // todo database operate
    throw new RuntimeException("innerFunction RuntimeException");
}

場景:內(nèi)層 PROPAGATION_REQUIRED,并拋出異常,外層事務(wù)捕獲異常

結(jié)果:全部回滾

原因:當(dāng)內(nèi)層事務(wù)異常的情況下,如果是 PROPAGATION_REQUIRED,正常來講是需要回滾的,但是 spring 只給內(nèi)層事務(wù)做了一個(gè) rollback 的標(biāo)記,當(dāng)內(nèi)層事務(wù)拋出的異常被外層捕獲時(shí),外層事務(wù)正常執(zhí)行,但在最后提交的時(shí)候發(fā)現(xiàn),內(nèi)層事務(wù)被標(biāo)記為 rollbck,所以就會拋出 UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only。

@Transactional(rollbackFor = Exception.class)
public void function() {
    // todo database operate
    try {
        serviceB.innerFunction();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

@Transactional(rollbackFor = Exception.class)
public void innerFunction() {
    // todo database operate
    throw new RuntimeException("innerFunction RuntimeException");
}

解決 UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only 的方案有兩個(gè):

  1. 內(nèi)層事務(wù)方法捕獲自己拋出的異常

    @Transactional(rollbackFor = Exception.class)
    public void innerFunction() {
        try {
            // todo database operate
            throw new RuntimeException("innerFunction RuntimeException");
        } catch (RuntimeException e) {
            e.printStackTrace();
        }
    }
    
  2. 將內(nèi)層事務(wù)傳播行為改為 PROPAGATION_REQUIRES_NEW,詳情見 2.2.3 Propagation.REQUIRES_NEW

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void innerFunction() {
        // todo database operate
        throw new RuntimeException("innerFunction RuntimeException");
    }
    
2.2.3 PROPAGATION_REQUIRES_NEW

創(chuàng)建一個(gè)新的事務(wù),如果當(dāng)前存在事務(wù),則把當(dāng)前事務(wù)掛起。也就是說不管外部方法是否開啟事務(wù),Propagation.REQUIRES_NEW 修飾的內(nèi)部方法會新開啟自己的事務(wù),且開啟的事務(wù)相互獨(dú)立,互不干擾

場景:內(nèi)層 PROPAGATION_REQUIRES_NEW,并拋出異常,外層事務(wù)不捕獲異常

結(jié)果:外層事務(wù)不回滾,內(nèi)層事務(wù)回滾

原因:內(nèi)外層事務(wù)互不影響,內(nèi)層事務(wù)的回滾不影響外層事務(wù)的正常執(zhí)行

@Transactional(rollbackFor = Exception.class)
public void function() {
    // todo database operate
    serviceB.innerFunction();
}

@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void innerFunction() {
    // todo database operate
    throw new RuntimeException("innerFunction RuntimeException");
}

場景:內(nèi)層 PROPAGATION_REQUIRES_NEW,并拋出異常,外層事務(wù)捕獲異常

結(jié)果:外層事務(wù)不回滾,內(nèi)層事務(wù)回滾

原因:內(nèi)外層事務(wù)互不影響,內(nèi)層事務(wù)的回滾不影響外層事務(wù)的正常執(zhí)行

@Transactional(rollbackFor = Exception.class)
public void function() {
    // todo database operate
    try {
        serviceB.innerFunction();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void innerFunction() {
    // todo database operate
    throw new RuntimeException("innerFunction RuntimeException");
}

場景:外層事務(wù)拋出異常,內(nèi)層事務(wù)正常執(zhí)行

結(jié)果:外層事務(wù)回滾,內(nèi)層事務(wù)不回滾

原因:內(nèi)外層事務(wù)互不影響,外層事務(wù)的回滾不影響內(nèi)層事務(wù)的正常執(zhí)行

@Transactional(rollbackFor = Exception.class)
public void function() {
    // todo database operate
    serviceB.innerFunction();
    throw new RuntimeException("function RuntimeException");
}

@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void innerFunction() {
    // todo database operate
}

場景:外層事務(wù)拋出異常并捕獲,內(nèi)層事務(wù)正常執(zhí)行

結(jié)果:內(nèi)外層事務(wù)均不回滾

原因:外層事務(wù)捕獲了 RuntimeException,因此不回滾

@Transactional(rollbackFor = Exception.class)
public void function() {
    // todo database operate
    try {
        serviceB.innerFunction();
        throw new RuntimeException("function RuntimeException");
    } catch (Exception e) {
        e.printStackTrace();
    }
}

@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void innerFunction() {
    // todo database operate
}
2.2.4 PROPAGATION_NESTED

如果當(dāng)前存在事務(wù),則創(chuàng)建一個(gè)事務(wù)作為當(dāng)前事務(wù)的嵌套事務(wù)來運(yùn)行;如果當(dāng)前沒有事務(wù),則該取值等價(jià)于 TransactionDefinition.PROPAGATION_REQUIRED。也就是說:

  1. 在外部方法未開啟事務(wù)的情況下 Propagation.NESTED 和 Propagation.REQUIRED 作用相同,修飾的內(nèi)部方法都會新開啟自己的事務(wù),且開啟的事務(wù)相互獨(dú)立,互不干擾。
  2. 如果外部方法開啟事務(wù)的話,Propagation.NESTED 修飾的內(nèi)部方法屬于外部事務(wù)的子事務(wù),外部主事務(wù)回滾的話,子事務(wù)也會回滾,而內(nèi)部子事務(wù)可以單獨(dú)回滾而不影響外部主事務(wù)和其他子事務(wù)

場景:外層事務(wù)方法 function 正常執(zhí)行,內(nèi)層事務(wù)方法 innerFunction1 執(zhí)行正常,內(nèi)存事務(wù)方法 innerFunction2 拋出異常

結(jié)果:全部回滾

原因:有人可能會問,不應(yīng)該是內(nèi)層事務(wù)的回滾不影響外層事務(wù)執(zhí)行嗎?為什么會全部回滾。原因在于 innerFunction2 拋出 RuntimeException 后,function 沒有進(jìn)行捕獲處理,因此該 RuntimeException 出發(fā)了 function 的 rollbackFor = {Exception.class} 條件,導(dǎo)致所以操作均回滾。正確的方式為對 innerFunction2 包一層 try-catch 語句,這樣就達(dá)到內(nèi)層事務(wù)回滾不影響外層事務(wù)了

// function 沒有捕獲 innerFunction2 拋出的異常,因此 function 也會回滾,這是錯(cuò)誤的打開方式
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
public void function() {
    // todo database operate
    serviceB.innerFunction1();
    serviceB.innerFunction2();
}

// 這才是正確的打開方式
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
public void function11() {
    // todo database operate
    try {
        serviceB.innerFunction1();
        serviceB.innerFunction2();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
public void innerFunction1() {
    // todo database operate
}

@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
public void innerFunction2() {
    // todo database operate
    throw new RuntimeException("innerFunction7 RuntimeException");
}

場景:外層事務(wù)方法 function 拋出異常,內(nèi)層事務(wù)方法 innerFunction1 和 innerFunction2 執(zhí)行正常

結(jié)果:全部回滾

原因:外層事務(wù)影響內(nèi)層事務(wù)

@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
public void function() {
    // todo database operate
    try {
        serviceB.innerFunction1();
        serviceB.innerFunction2();
    } catch (Exception e) {
        e.printStackTrace();
    }
    throw new RuntimeException("function RuntimeException");
}

@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
public void innerFunction1() {
    // todo database operate
}

@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
public void innerFunction2() {
    // todo database operate
}
2.2.5 PROPAGATION_MANDATORY

如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則拋出異常。(mandatory:強(qiáng)制性)

事務(wù)隔離級別

TransactionDefinition 接口中定義了五個(gè)表示隔離級別的常量:

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;
    ......
}
事務(wù)隔離界別 說明
ISOLATION_DEFAULT 使用后端數(shù)據(jù)庫默認(rèn)的隔離級別,MySQL 默認(rèn)采用的 REPEATABLE_READ 隔離級別,Oracle 默認(rèn)采用的 READ_COMMITTED 隔離級別
ISOLATION_READ_UNCOMMITTED 最低的隔離級別,使用這個(gè)隔離級別很少,因?yàn)樗试S讀取尚未提交的數(shù)據(jù)變更,可能會導(dǎo)致臟讀、幻讀或不可重復(fù)讀
ISOLATION_READ_COMMITTED 允許讀取并發(fā)事務(wù)已經(jīng)提交的數(shù)據(jù),可以阻止臟讀,但是幻讀或不可重復(fù)讀仍有可能發(fā)生
ISOLATION_REPEATABLE_READ 同一字段的多次讀取結(jié)果都是一致的,除非數(shù)據(jù)是被本身事務(wù)自己所修改,可以阻止臟讀和不可重復(fù)讀,但幻讀仍有可能發(fā)生
ISOLATION_SERIALIZABLE 最高的隔離級別,完全服從 ACID 的隔離級別。所有的事務(wù)依次逐個(gè)執(zhí)行,這樣事務(wù)之間就完全不可能產(chǎn)生干擾,也就是說,該級別可以防止臟讀、不可重復(fù)讀以及幻讀。但是這將嚴(yán)重影響程序的性能。通常情況下也不會用到該級別

MySQL InnoDB 存儲引擎的默認(rèn)支持的隔離級別是 REPEATABLE-READ(可重讀)。我們可以通過 SELECT @@tx_isolation; 命令來查看

這里需要注意的是:與 SQL 標(biāo)準(zhǔn)不同的地方在于 InnoDB 存儲引擎在 REPEATABLE-READ(可重讀) 事務(wù)隔離級別下使用的是 Next-Key Lock 鎖算法,因此可以避免幻讀的產(chǎn)生,這與其他數(shù)據(jù)庫系統(tǒng)(如 SQL Server)是不同的。所以說 InnoDB 存儲引擎的默認(rèn)支持的隔離級別是 REPEATABLE-READ(可重讀) 已經(jīng)可以保證事務(wù)的隔離性要求,即達(dá)到了 SQL 標(biāo)準(zhǔn)的 SERIALIZABLE(可串行化) 隔離級別

事務(wù)超時(shí)屬性

所謂事務(wù)超時(shí),就是指一個(gè)事務(wù)所允許執(zhí)行的最長時(shí)間,如果超過該時(shí)間限制但事務(wù)還沒有完成,則自動(dòng)回滾事務(wù)。在 TransactionDefinition 中以 int 的值來表示超時(shí)時(shí)間,其單位是秒,默認(rèn)值為-1

事務(wù)只讀屬性

public interface TransactionDefinition {
    ......
    // 返回是否為只讀事務(wù),默認(rèn)值為 false
    boolean isReadOnly();
}

對于只有讀取數(shù)據(jù)查詢的事務(wù),可以指定事務(wù)類型為 readonly,即只讀事務(wù)。只讀事務(wù)不涉及數(shù)據(jù)的修改,數(shù)據(jù)庫會提供一些優(yōu)化手段,適合用在有多條數(shù)據(jù)庫查詢操作的方法中

很多人就會疑問了,為什么我一個(gè)數(shù)據(jù)查詢操作還要啟用事務(wù)支持呢?

拿 MySQL 的 innodb 舉例子,根據(jù)官網(wǎng) https://dev.mysql.com/doc/refman/5.7/en/innodb-autocommit-commit-rollback.html 描述:

MySQL 默認(rèn)對每一個(gè)新建立的連接都啟用了autocommit模式。在該模式下,每一個(gè)發(fā)送到 MySQL 服務(wù)器的sql語句都會在一個(gè)單獨(dú)的事務(wù)中進(jìn)行處理,執(zhí)行結(jié)束后會自動(dòng)提交事務(wù),并開啟一個(gè)新的事務(wù)。

但是,如果你給方法加上了 @Transactional 注解的話,這個(gè)方法執(zhí)行的所有sql會被放在一個(gè)事務(wù)中。如果聲明了只讀事務(wù)的話,數(shù)據(jù)庫就會去優(yōu)化它的執(zhí)行,并不會帶來其他的什么收益。

如果不加 @Transactional,每條 sql 會開啟一個(gè)單獨(dú)的事務(wù),中間被其它事務(wù)改了數(shù)據(jù),都會實(shí)時(shí)讀取到最新值

分享一下關(guān)于事務(wù)只讀屬性,其他人的解答:

  1. 如果你一次執(zhí)行單條查詢語句,則沒有必要啟用事務(wù)支持,數(shù)據(jù)庫默認(rèn)支持 SQL 執(zhí)行期間的讀一致性
  2. 如果你一次執(zhí)行多條查詢語句,例如統(tǒng)計(jì)查詢,報(bào)表查詢,在這種場景下,多條查詢 SQL 必須保證整體的讀一致性,否則,在前條 SQL 查詢之后,后條 SQL 查詢之前,數(shù)據(jù)被其他用戶改變,則該次整體的統(tǒng)計(jì)查詢將會出現(xiàn)讀數(shù)據(jù)不一致的狀態(tài),此時(shí),應(yīng)該啟用事務(wù)支持

事務(wù)回滾規(guī)則

默認(rèn)情況下,事務(wù)只有遇到運(yùn)行期異常(RuntimeException 的子類)時(shí)才會回滾,Error 也會導(dǎo)致事務(wù)回滾,但是,在遇到檢查型(Checked)異常時(shí)不會回滾

事務(wù)失效場景

一、數(shù)據(jù)庫引擎是否支持事務(wù)

Mysql 的 MyIsam 引擎不支持事務(wù)

二、注解所在的類是否注入 spring 容器中

三、注解所在方法不是 public 或者是 final

這是由 Spring AOP 的本質(zhì)決定的。如果你在 protected、private 或者默認(rèn)可見性的方法上使用 @Transactional 注解,這將被忽略,也不會拋出任何異常。

在 spring 中動(dòng)態(tài)代理分為 JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理,JDK 動(dòng)態(tài)代理要求必須實(shí)現(xiàn)接口(所以方法必須是public的),但是 CGLIB 動(dòng)態(tài)代理底層則是通過字節(jié)碼生成被代理類的子類來實(shí)現(xiàn)的,這里要求被代理類必須能被繼承(public 和 protected),被 final 修飾的方法不能被子類繼承,因此 @Transactional 注解無效。但為何 @Transactional 注解不支持 protected 方法呢?

spring 官方文檔中有如下說明:

Spring AOP 對 privateprotect 是不支持的,無論是 JDK 還是 CGLIB,如果要對 protect 方法進(jìn)行攔截,建議使用 AspectJ

不清楚 Spring 為什么不推薦其 AOP 對 protect 不支持,猜測可能:

  1. 代理行為本身就是一種三方調(diào)用的思想,那么被代理的方法本身應(yīng)該是公有的
  2. 為了跟讓 CGLIB 和 JDK 保持一致,因?yàn)?JDK 基于接口的肯定都是 public 的,而 CGLIB 干嘛搞特殊?
  3. 待續(xù)猜想

四、所用數(shù)據(jù)源是否加載了事務(wù)管理器

五、事務(wù)自調(diào)用(同一個(gè)類中的 A 方法調(diào)用 B 方法)

若同一類中的其他沒有 @Transactional 注解的方法內(nèi)部調(diào)用有 @Transactional 注解的方法,有 @Transactional 注解的方法的事務(wù)被忽略,不會發(fā)生回滾

使用 AOP 代理后的方法調(diào)用執(zhí)行流程,如圖所示,可以看到調(diào)用者首先調(diào)用的是 AOP 代理對象而不是目標(biāo)對象,首先執(zhí)行事務(wù)切面,事務(wù)切面內(nèi)部通過 TransactionInterceptor 環(huán)繞增強(qiáng)進(jìn)行事務(wù)的增強(qiáng),即進(jìn)入目標(biāo)方法之前開啟事務(wù),退出目標(biāo)方法時(shí)提交/回滾事務(wù)

目標(biāo)對象內(nèi)部的自我調(diào)用將無法實(shí)施切面中的增強(qiáng),如圖所示,this 指向目標(biāo)對象,因此調(diào)用 this.b() 將不會執(zhí)行 b 事務(wù)切面,即不會執(zhí)行事務(wù)增強(qiáng)

六、當(dāng)方法發(fā)生異常時(shí),使用 try-catch 捕獲了異常,并且 catch 中沒有拋出異常或者手動(dòng)回滾

事務(wù)的回滾是方法發(fā)生異常,在 aop 的異常通知中進(jìn)行攔截回滾。如果方法中捕獲了異常,是不會被 aop 的異常通知攔截到的。如果使用 try-catch 捕獲異常,需要在catch中拋出一個(gè)異?;蛘咴?catch 中通過 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() 設(shè)置手動(dòng)回滾

@Transactional 事務(wù)注解原理

@Transactional 的工作機(jī)制是基于 AOP 實(shí)現(xiàn)的,AOP 又是使用動(dòng)態(tài)代理實(shí)現(xiàn)的。如果目標(biāo)對象實(shí)現(xiàn)了接口,默認(rèn)情況下會采用 JDK 的動(dòng)態(tài)代理,如果目標(biāo)對象沒有實(shí)現(xiàn)接口,會使用 CGLIB 動(dòng)態(tài)代理

createAopProxy 方法 決定了是使用 JDK 還是 Cglib 來做動(dòng)態(tài)代理,源碼如下:

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            ...
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            return new ObjenesisCglibAopProxy(config);
        } else {
            return new JdkDynamicAopProxy(config);
        }
    }
    .......
}

如果一個(gè)類或者一個(gè)類中的 public 方法上被標(biāo)注 @Transactional 注解的話,Spring 容器就會在啟動(dòng)的時(shí)候?yàn)槠鋭?chuàng)建一個(gè)代理類,在調(diào)用被 @Transactional 注解的 public 方法的時(shí)候,實(shí)際調(diào)用的是,TransactionInterceptor 類中的 invoke 方法。這個(gè)方法的作用就是在目標(biāo)方法之前開啟事務(wù),方法執(zhí)行過程中如果遇到異常的時(shí)候回滾事務(wù),方法調(diào)用完成之后提交事務(wù)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容