Spring事務傳播屬性有那麼難理解嗎?

學習東西要知行合一,若是隻是知道理論而沒實踐過,那麼掌握的也不會特別紮實,估計過幾天就會忘記,接下來咱們一塊兒實踐來學習Spring事務的傳播屬性。程序員

傳播屬性

傳播屬性定義的是當一個事務方法碰到另外一個事務方法時的處理行爲,一共有七種行爲,定義以下spring

傳播性 描述
PROPAGATION_REQUIRED 0 支持當前事務,若是沒有就新建事務
PROPAGATION_SUPPORTS 1 支持當前事務,若是沒有就不以事務的方式運行
PROPAGATION_MANDATORY 2 支持當前事務,若是當前沒事務就拋異常
PROPAGATION_REQUIRES_NEW 3 不管當前是否有事務,都會新起一個事務
PROPAGATION_NOT_SUPPORTED 4 不支持事務,若是當前存在事務,就將此事務掛起不以事務方式運行
PROPAGATION_NEVER 5 不支持事務,若是有事務就拋異常
PROPAGATION_NESTED 6 若是當前存在事務,在當前事務中再新起一個事務

其實只看概念的話已經很直截了當了說明了每一個傳播性的做用,此時咱們再用具體的例子演示一下每一個傳播性屬性下的行爲。sql

這次演示咱們使用的是H2數據庫,這個數據庫是做用在內存裏面的,因此對於咱們演示事務效果來講正好,無需咱們在進行其餘的配置了,咱們新建一個表。將下面語句放在schema.sql文件裏面便可,SpringBoot程序在啓動的時候就會自動爲咱們在內存裏面創建這樣的一個表。數據庫

CREATE TABLE FOO (ID INT IDENTITY, BAR VARCHAR(64));複製代碼

演示以前咱們會定義兩個類FooServiceBarService。咱們使用BarService裏面的方法進行調用FooService中的方法。後端

環境準備

在進行事務演示以前,其實能夠分爲如下幾種狀況,根據排列組合,咱們能夠得出如下八種狀況bash

  • 調用者:有無事務
  • 調用者:是否有異常
  • 被調用者:有無事務**(這個是經過傳播屬性進行控制的)**因此並不在排列組合中
  • 被調用者:是否有異常
調用者是否有事務 調用者是否有異常 被調用者是否有異常

異常類

其中的RollbackException是咱們本身定義的一個異常類微信

@Service
public class BarServiceImpl implements BarService{
    @Autowired
    private FooService fooService;
 	// PROPAGATION_REQUIRED演示 無事務
    @Override
    public void testRequiredNoTransactional() throws RollbackException {
        fooService.testRequiredTransactional();
    }
}
複製代碼

調用者

BarService中定義兩個方法,一個是帶着事務的,一個是不帶事務的前後端分離

// 有事務
@Override
@Transactional(rollbackFor = Exception.class)
public void hasTransactional() throws RollbackException {
}
// 無事務
@Override
public void noTransactional() throws RollbackException {   
}
複製代碼

接下來咱們就根據俄上面定義的八種狀況進行事務傳播屬性的學習。ide

PROPAGATION_REQUIRED

在此傳播屬性下,被調用方是否新建事務取決去調用者是否帶着事務。微服務

想要了解這個傳播屬性的特性,其實咱們演示上面八種狀況的兩個例子就夠了

調用者是否有事務 調用者是否有異常 被調用者是否有異常
  • 第一種狀況咱們在被調用者拋出異常的狀況下,若是查詢不到插入的數據,那麼就說明被調用者在調用者沒有事務的狀況下本身新建了事務。
  • 第二種狀況咱們在調用者拋出異常的狀況下,若是查詢不到插入的數據,那麼就說明被調用者在調用者有事務的狀況下就加入當前事務了。

咱們先來看一下被調用者的類的方法例子。

@Service
public class FooServiceImpl implements FooService {
    @Autowired
    private JdbcTemplate jdbcTemplate;    
    // REQUIRED傳播屬性-被調用者有異常拋出
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void testRequiredHasException() throws RollbackException {
        jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ("+Global.REQUIRED_HAS_EXCEPTION+")");
        throw new RollbackException();
    }
    // REQUIRED傳播屬性-被調用者無異常拋出
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void testRequiredNoException() throws RollbackException {
        jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ("+Global.REQUIRED_NO_EXCEPTION+")");
    }
}
複製代碼

接下來咱們看一下調用者方法的例子

@Service
public class BarServiceImpl implements BarService{
    @Autowired
    private FooService fooService;
    // 有事務
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void hasTransactional() throws RollbackException {
        // 調用者有事務,拋異常  被調用者無異常
        fooService.testRequiredNoException();
        throw new RollbackException();
    }
    // 無事務
    @Override
    public void noTransactional() throws RollbackException {
        // 調用者無事務,不拋異常  被調用者有異常
        fooService.testRequiredHasException();
    }
}
複製代碼

此時咱們在程序調用時進行查詢

String noException = Global.REQUIRED_NO_EXCEPTION;
String hasException = Global.REQUIRED_HAS_EXCEPTION;
try {
    barService.noTransactional();
}catch (Exception e){
    log.info("第一種狀況 {}",
            jdbcTemplate
                    .queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='"+hasException+"'", Long.class));
}
try {
    barService.hasTransactional();
}catch (Exception e){
    log.info("第二種狀況 {}",
            jdbcTemplate
                    .queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='"+noException+"'", Long.class));
}
複製代碼

查看打印出來的日誌

2019-10-16 13:02:04.142  INFO 11869 --- [           main] c.e.t.t.TransactionApplication           : 第一種狀況 0
2019-10-16 13:02:04.143  INFO 11869 --- [           main] c.e.t.t.TransactionApplication           : 第二種狀況 0
複製代碼

咱們看到咱們都沒有查到相應的數據,說明數據都回滾了。此時咱們應該就理解了那句話支持當前事務,若是沒有就新建事務

PROPAGATION_SUPPORTS

被調用者是否有事務,徹底依賴於調用者,調用者有事務則有事務,調用者沒事務則沒事務。

接下來咱們仍是用上面的兩個例子進行演示

調用者是否有事務 調用者是否有異常 被調用者是否有異常
  • 第一種狀況:被調用者拋出異常的狀況下,若是仍能查詢到數據,說明事務沒有回滾,說明被調用者沒有事務
  • 第二種狀況:調用者拋出異常狀況下,若是查不到數據,說明兩個方法在一個事務中

接下來仍然是例子演示

被調用者,只是將@Transactional註解中的propagation屬性更換爲了Propagation.SUPPORTS

// SUPPORTS傳播屬性-被調用者有異常拋出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.SUPPORTS)
public void testSupportsHasException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_HAS_EXCEPTION+"')");
    throw new RollbackException();
}
// SUPPORTS傳播屬性-被調用者無異常拋出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.SUPPORTS)
public void testSupportsNoException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_NO_EXCEPTION+"')");
}
複製代碼

調用者和上面的例子調用同樣,咱們直接查看執行效果

2019-10-16 13:50:27.738  INFO 12174 --- [           main] c.e.t.t.TransactionApplication           : 第一種狀況 1
2019-10-16 13:50:27.741  INFO 12174 --- [           main] c.e.t.t.TransactionApplication           : 第二種狀況 0
複製代碼

咱們看到了在第一種狀況下查到了數據,說明在第一種狀況下被調用者是沒有事務的。此時咱們應該就理解了這句話 支持當前事務,若是沒有就不以事務的方式運行

PROPAGATION_MANDATORY

依然是這兩個例子進行演示

調用者是否有事務 調用者是否有異常 被調用者是否有異常
  • 第一種狀況:由於調用者沒有事務,因此此傳播屬性下應該是拋異常的
  • 第二種狀況:被調用者的事務和調用者事務是一樣的

接下來是被調用者的代碼例子

// MANDATORY傳播屬性-被調用者有異常拋出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.MANDATORY)
public void testMandatoryHasException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_HAS_EXCEPTION+"')");
    throw new RollbackException();
}
// MANDATORY傳播屬性-被調用者無異常拋出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.MANDATORY)
public void testMandatoryNoException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_NO_EXCEPTION+"')");
}
複製代碼

調用者和上面的例子調用同樣,咱們直接查看執行效果

2019-10-16 13:58:39.178 ERROR 12317 --- [           main] c.e.t.t.TransactionApplication           : org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
2019-10-16 13:58:39.276  INFO 12317 --- [           main] c.e.t.t.TransactionApplication           : 第一種狀況 0
2019-10-16 13:58:39.281  INFO 12317 --- [           main] c.e.t.t.TransactionApplication           : 第二種狀況 0
複製代碼

咱們發現和咱們推測同樣,說明被調用者是不會本身新建事務的,此時咱們應該就理解了這句話支持當前事務,若是當前沒事務就拋異常

PROPAGATION_REQUIRES_NEW

此傳播屬性下,不管調用者是否有事務,被調用者都會新建一個事務

調用者是否有事務 調用者是否有異常 被調用者是否有異常
  • 第一種狀況:調用者無事務,被調用者會新建事務,因此查不到數據
  • 第二種狀況:調用者有事務,被調用者會新建一個事務,因此調用者拋異常影響不到被調用者,因此能查到數據

接下來咱們演示代碼。

被調用者

// REQUIRES_NEW傳播屬性-被調用者有異常拋出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
public void testRequiresNewHasException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.REQUIRES_NEW_HAS_EXCEPTION+"')");
    throw new RollbackException();
}
// REQUIRES_NEW傳播屬性-被調用者無異常拋出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
public void testRequiresNewNoException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.REQUIRES_NEW_NO_EXCEPTION+"')");
}
複製代碼

調用者的例子和上面的相同,咱們直接來看執行狀況

2019-10-16 16:29:20.296  INFO 15553 --- [           main] c.e.t.t.TransactionApplication           : 第一種狀況 0
2019-10-16 16:29:20.298  INFO 15553 --- [           main] c.e.t.t.TransactionApplication           : 第二種狀況 1
複製代碼

咱們發現和咱們的推論是同樣的,說明調用者的事務和被調用者的事務徹底無關。此時咱們應該就理解這句話了不管當前是否有事務,都會新起一個事務

PROPAGATION_NOT_SUPPORTED

不管調用者是否有事務,被調用者都不以事務的方法運行

一樣是這兩個例子

調用者是否有事務 調用者是否有異常 被調用者是否有異常
  • 第一種狀況:被調用者都不會有事務,那麼在拋異常以後就能查到相應的數據
  • 第二種狀況:在調用者有事務的狀況下,被調用者也會在無事務環境下運行,因此咱們依然能查到數據

接下來驗證咱們的猜想

// NOT_SUPPORTED傳播屬性-被調用者有異常拋出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
public void testNotSupportHasException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NOT_SUPPORTS_HAS_EXCEPTION+"')");
    throw new RollbackException();
}
// NOT_SUPPORTED傳播屬性-被調用者無異常拋出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
public void testNotSupportNoException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NOT_SUPPORTS_NO_EXCEPTION+"')");
}
複製代碼

而後查看執行結果

2019-10-16 16:38:35.065  INFO 15739 --- [           main] c.e.t.t.TransactionApplication           : 第一種狀況 1
2019-10-16 16:38:35.067  INFO 15739 --- [           main] c.e.t.t.TransactionApplication           : 第二種狀況 1
複製代碼

咱們能夠看到在最後兩種狀況都查到了數據,根據演示效果應該能夠理解這句話了,不支持事務,若是當前存在事務,就將此事務掛起不以事務方式運行

PROPAGATION_NEVER

調用者有事務,被調用者就會拋出異常

調用者是否有事務 調用者是否有異常 被調用者是否有異常

這個就不演示,相信你們看到這裏應該都會明白在第一種狀況下咱們是可以查到數據的。在第二種狀況下因爲調用者帶着事務,因此會拋異常。

PROPAGATION_NESTED

此傳播屬性下,被調用者的事務是調用者的事務的子集。

咱們重點說一下NESTED的傳播屬性的特性

調用者是否有事務 說明
被調用者會新起一個事務,此事務和調用者事務是一個嵌套的關係
被調用者會本身新起一個事務

關於什麼是嵌套事務的關係,咱們用下面三個例子可以進行演示。

調用者是否有事務 調用者是否有異常 被調用者是否有異常
  • 第一種狀況:若是查不到數據,則說明在調用者無事務狀況下,被調用者會新起一個事務
  • 第二種狀況:若是查不到數據,說明外層事務可以影響內層事務
  • 第三種狀況:若是查到數據,說明內層事務不影響外層事務

接下來咱們編寫具體的代碼

// NESTED傳播屬性-回滾事務
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
public void testNestedHasException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NESTED_HAS_EXCEPTION+"')");
   // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    throw new RollbackException();
}
// NESTED傳播屬性-不回滾事務
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
public void testNestedNoException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NESTED_NO_EXCEPTION+"')");
}
複製代碼

而後接下來的調用者也會有點區別

@Override
@Transactional()
public void hasTransactionalNoException() throws RollbackException {
    // NESTED傳播屬性 - 調用者有事務,不拋異常  被調用者有異常
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NESTED_HAS_EXCEPTION_TWO+"')");
    fooService.testNestedHasException();
}
複製代碼

而後執行效果

2019-10-16 18:01:06.387  INFO 17172 --- [           main] c.e.t.t.TransactionApplication           : 第一種狀況 0
2019-10-16 18:01:06.389  INFO 17172 --- [           main] c.e.t.t.TransactionApplication           : 第二種狀況 0
2019-10-16 18:01:06.390  INFO 17172 --- [           main] c.e.t.t.TransactionApplication           : 第三種狀況 1
複製代碼

能夠看出來嵌套事務的本質就是外層會影響內層,內層不影響外層。而REQUIRES_NEW則是互不影響

總結

到如今咱們已經所有分析完了七種傳播屬性,從寫這篇文章開始到結束其中也碰到過一些坑,有些是不本身實踐一遍是根本不知道的,因此我仍是建議讀者看完這篇文章之後本身進行實踐,演示各類狀況,只有這樣纔可以爛熟於心。



關注微信公衆號【程序員的夢想】,專一於Java,SpringBoot,SpringCloud,微服務,Docker以及先後端分離等全棧技術。

在這裏插入圖片描述
相關文章
相關標籤/搜索