Spring Boot 中使用 @Transactional 註解配置事務管理

事務管理是應用系統開發中必不可少的一部分。Spring 爲事務管理提供了豐富的功能支持。Spring 事務管理分爲編程式和聲明式的兩種方式。編程式事務指的是經過編碼方式實現事務;聲明式事務基於 AOP,將具體業務邏輯與事務處理解耦。聲明式事務管理使業務代碼邏輯不受污染, 所以在實際使用中聲明式事務用的比較多。聲明式事務有兩種方式,一種是在配置文件(xml)中作相關的事務規則聲明,另外一種是基於 @Transactional 註解的方式。本文將着重介紹基於 @Transactional 註解的事務管理。html

須要明確幾點:java

  1. 默認配置下 Spring 只會回滾運行時、未檢查異常(繼承自 RuntimeException 的異常)或者 Error。參考這裏
  2. @Transactional 註解只能應用到 public 方法纔有效。參考這裏 Method visibility and @Transactional

如下的示例使用的是 mybatis,因此 spring boot 會自動配置一個 DataSourceTransactionManager,咱們只需在方法(或者類)加上 @Transactional 註解,就自動歸入 Spring 的事務管理了。git

簡單的使用方法

只需在方法加上 @Transactional 註解就能夠了。github

以下有一個保存用戶的方法,加入 @Transactional 註解,使用默認配置,拋出異常以後,事務會自動回滾,數據不會插入到數據庫。spring

@Transactional
@Override
public void save() {
    User user = new User("服部半藏");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 拋異常了");
    }
}

咱們能夠從日誌裏面看出這些信息數據庫

TIM截圖20171129135813

@Transactional 註解的屬性介紹

下面分別介紹一下 @Transactional 的幾個屬性。編程

value 和 transactionManager 屬性

它們兩個是同樣的意思。當配置了多個事務管理器時,可使用該屬性指定選擇哪一個事務管理器。springboot

propagation 屬性

事務的傳播行爲,默認值爲 Propagation.REQUIRED。mybatis

可選的值有:app

  • Propagation.REQUIRED

    若是當前存在事務,則加入該事務,若是當前不存在事務,則建立一個新的事務。

  • Propagation.SUPPORTS

    若是當前存在事務,則加入該事務;若是當前不存在事務,則以非事務的方式繼續運行。

  • Propagation.MANDATORY

    若是當前存在事務,則加入該事務;若是當前不存在事務,則拋出異常。

  • Propagation.REQUIRES_NEW

    從新建立一個新的事務,若是當前存在事務,暫停當前的事務。

  • Propagation.NOT_SUPPORTED

    以非事務的方式運行,若是當前存在事務,暫停當前的事務。

  • Propagation.NEVER

    以非事務的方式運行,若是當前存在事務,則拋出異常。

  • Propagation.NESTED

    和 Propagation.REQUIRED 效果同樣。

這些概念理解起來實在是有點兒抽象,後文會用代碼示例解釋說明。

isolation 屬性

事務的隔離級別,默認值爲 Isolation.DEFAULT。

可選的值有:

  • Isolation.DEFAULT

    使用底層數據庫默認的隔離級別。

  • Isolation.READ_UNCOMMITTED

  • Isolation.READ_COMMITTED

  • Isolation.REPEATABLE_READ

  • Isolation.SERIALIZABLE

timeout 屬性

事務的超時時間,默認值爲-1。若是超過該時間限制但事務尚未完成,則自動回滾事務。

readOnly 屬性

指定事務是否爲只讀事務,默認值爲 false;爲了忽略那些不須要事務的方法,好比讀取數據,能夠設置 read-only 爲 true。

rollbackFor 屬性

用於指定可以觸發事務回滾的異常類型,能夠指定多個異常類型。

noRollbackFor 屬性

拋出指定的異常類型,不回滾事務,也能夠指定多個異常類型。

@Transactional 的 propagation 屬性代碼示例

好比以下代碼,save 方法首先調用了 method1 方法,而後拋出了異常,就會致使事務回滾,以下兩條數據都不會插入數據庫。

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

    method1();

    User user = new User("服部半藏");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 拋異常了");
    }
}

public void method1() {
    User user = new User("宮本武藏");
    userMapper.insertSelective(user);
}

如今有需求以下,就算 save 方法的後面拋異常了,也不能影響 method1 方法的數據插入。或許不少人的想法以下,給 method1 頁加入一個新的事務,這樣 method1 就會在這個新的事務中執行,原來的事務不會影響到新的事務。好比 method1 方法上面再加入註解 @Transactional,設置 propagation 屬性爲 Propagation.REQUIRES_NEW,代碼以下。

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

    method1();

    User user = new User("服部半藏");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 拋異常了");
    }
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method1() {
    User user = new User("宮本武藏");
    userMapper.insertSelective(user);
}

運行以後,發現然並卵,數據也是沒有插入數據庫。怎麼肥四,看起來很不科學。咱們先來看看日誌內容。

TIM截圖20171129150737

從日誌內容能夠看出,其實兩個方法都是處於同一個事務中,method1 方法並無建立一個新的事務。

這就得看看 Spring 官方文檔了。

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.

大概意思:在默認的代理模式下,只有目標方法由外部調用,才能被 Spring 的事務攔截器攔截。在同一個類中的兩個方法直接調用,是不會被 Spring 的事務攔截器攔截,就像上面的 save 方法直接調用了同一個類中的 method1方法,method1 方法不會被 Spring 的事務攔截器攔截。可使用 AspectJ 取代 Spring AOP 代理來解決這個問題,可是這裏暫不討論。

爲了解決這個問題,咱們能夠新建一個類。

@Service
public class OtherServiceImpl implements OtherService {

    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void method1() {
        User user = new User("風魔小太郎");
        userMapper.insertSelective(user);
    }
}

而後在 save 方法中調用 otherService.method1 方法

@Autowired
private OtherService otherService;

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

    otherService.method1();

    User user = new User("服部半藏");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 拋異常了");
    }
}

這下,otherService.method1 方法的數據插入成功,save 方法的數據未插入,事務回滾。

繼續看一下日誌內容

TIM截圖20171129153731

從日誌能夠看出,首先建立了 save 方法的事務,因爲 otherService.method1 方法的 @Transactional 的 propagation 屬性爲 Propagation.REQUIRES_NEW ,因此接着暫停了 save 方法的事務,從新建立了 otherService.method1 方法的事務,接着 otherService.method1 方法的事務提交,接着 save 方法的事務回滾。這就印證了只有目標方法由外部調用,才能被 Spring 的事務攔截器攔截。

還有幾個示例以下。

接着把 save 方法的 @Transactional 註解去掉,otherService.method1 的 @Transactional 註解保持不變,從日誌就能夠看出,只會建立一個 otherService.method1 方法的事務,兩條數據都會插入。

@Autowired
private OtherService otherService;

//    @Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

    otherService.method1();

    User user = new User("服部半藏");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 拋異常了");
    }
}

接着把 save 方法的 @Transactional 註解去掉,save 方法改成調用內部的 method1 方法,從日誌就能夠看出,徹底沒有建立任何事務,兩條數據都會插入。

//    @Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

    method1();

    User user = new User("服部半藏");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 拋異常了");
    }
}


@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method1() {
    User user = new User("宮本武藏");
    userMapper.insertSelective(user);
}

這樣,其餘的幾個 propagation 屬性值也就比較好理解了。

@Transactional 事務實現機制

在應用系統調用聲明瞭 @Transactional 的目標方法時,Spring Framework 默認使用 AOP 代理,在代碼運行時生成一個代理對象,根據 @Transactional 的屬性配置信息,這個代理對象決定該聲明 @Transactional 的目標方法是否由攔截器 TransactionInterceptor 來使用攔截,在 TransactionInterceptor 攔截時,會在目標方法開始執行以前建立並加入事務,並執行目標方法的邏輯, 最後根據執行狀況是否出現異常,利用抽象事務管理器 AbstractPlatformTransactionManager 操做數據源 DataSource 提交或回滾事務。

Spring AOP 代理有 CglibAopProxyJdkDynamicAopProxy 兩種,以 CglibAopProxy 爲例,對於 CglibAopProxy,須要調用其內部類的 DynamicAdvisedInterceptor 的 intercept 方法。對於 JdkDynamicAopProxy,須要調用其 invoke 方法。

Spring-transaction-mechanis

正如上文提到的,事務管理的框架是由抽象事務管理器 AbstractPlatformTransactionManager 來提供的,而具體的底層事務處理實現,由 PlatformTransactionManager 的具體實現類來實現,如事務管理器 DataSourceTransactionManager。不一樣的事務管理器管理不一樣的數據資源 DataSource,好比 DataSourceTransactionManager 管理 JDBC 的 Connection

Spring-TransactionManager-hierarchy-subtypes

源碼地址

參考資料

結語

因爲本人知識和能力有限,文中若有沒說清楚的地方,但願你們能在評論區指出,以幫助我將博文寫得更好。

相關文章
相關標籤/搜索