面試官:說說Spring中的事務傳播行為


前言

在開發中,相信大家都使用過Spring的事務管理功能。那么,你是否有了解過,Spring的事務傳播行為呢?

Spring中,有7種類型的事務傳播行為。事務傳播行為是Spring框架提供的一種事務管理方式,它不是數據庫提供的。不知道大家是否聽說過“不要在service事務方法中嵌套事務方法,這樣會提交多個事務”的說法,其實這是不准確的。了解了事務傳播行為之后,相信你就會明白!

原創聲明

本文首發於頭條號【Happyjava】。Happy的掘金地址:https://juejin.im/user/5cc2895df265da03a630ddca,Happy的個人博客:(http://blog.happyjava.cn)[http://blog.happyjava.cn]。歡迎轉載,但須保留此段聲明。

Spring中七種事務傳播行為

事務的傳播行為,默認值為 Propagation.REQUIRED。可以手動指定其他的事務傳播行為,如下:

  • Propagation.REQUIRED

如果當前存在事務,則加入該事務,如果當前不存在事務,則創建一個新的事務。

  • Propagation.SUPPORTS

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

  • Propagation.MANDATORY

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

  • Propagation.REQUIRES_NEW

重新創建一個新的事務,如果當前存在事務,延緩當前的事務。

  • Propagation.NOT_SUPPORTED

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

  • Propagation.NEVER

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

  • Propagation.NESTED

如果沒有,就新建一個事務;如果有,就在當前事務中嵌套其他事務。

准備工作

數據庫表:

CREATE TABLE `t_user` (
  `id` int(11) NOT NULL,
  `password` varchar(255) DEFAULT NULL,
  `username` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

一個整合了Spring Data JPA的SpringBoot工程,這里就不多說了。

REQUIRED(默認的事務傳播行為)

默認的事務傳播行為是Propagation.REQUIRED,也就是說:如果當前存在事務,則加入該事務,如果當前不存在事務,則創建一個新的事務。

下面,我們就驗證下前面說的“不要循環嵌套事務方法”的問題:

現在有兩個Service,如下:

UserService.java

@Service
public class UserService {

    @Autowired
    private UserRepo userRepo;

    @Transactional(propagation = Propagation.REQUIRED)
    public void insert() {
        UserEntity user = new UserEntity();
        user.setUsername("happyjava");
        user.setPassword("123456");
        userRepo.save(user);
    }


}

這里很簡單,就一個insert插入用戶的方法。

UserService2.java

@Service
public class UserService2 {

    @Autowired
    private UserService userService;


    @Transactional
    public void inserBatch() {
        for (int i = 0; i < 10; i++) {
            if (i == 9) {
                throw new RuntimeException();
            }
            userService.insert();
        }
    }

}

注入UserService,循環十次調用參數方法。並且第十次拋出異常。調用inserBatch方法,查看結果:

@Test
public void insertBatchTest() {
    userService2.inserBatch();
}

結果如下:

數據庫中沒有記錄:

這也證明了“如果當前存在事務,則加入該事務”的概念。如果以后還碰到有人說不要循環嵌套事務的話,可以叫他回去好好看看Spring的事務傳播行為。

SUPPORTS

如果當前存在事務,則加入該事務;如果當前不存在事務,則以非事務的方式繼續運行。也就是說,該模式是否支持事務,看調用它的方法是否有事務支持。測試代碼如下:

UserService

@Transactional(propagation = Propagation.SUPPORTS)
public void insert() {
    UserEntity user = new UserEntity();
    user.setUsername("happyjava");
    user.setPassword("123456");
    userRepo.save(user);
    throw new RuntimeException();
}

UserService2

public void insertWithoutTx() {
    userService.insert();
}

調用的方法沒有開啟事務,運行結果:

運行報錯了,但是數據卻沒有回滾掉。說明了insert方法是沒有在事務中運行的。

MANDATORY

如果當前存在事務,則加入該事務;如果當前不存在事務,則拋出異常。mandatory中文是強制性的意思,表明了被修飾的方法,一定要在事務中去調用,否則會拋出異常。

UserService.java

@Transactional(propagation = Propagation.MANDATORY)
public void insert() {
    UserEntity user = new UserEntity();
    user.setUsername("happyjava");
    user.setPassword("123456");
    userRepo.save(user);
}

UserService2.java

public void insertWithoutTx() {
	userService.insert();
}

調用:

@Test
public void insertWithoutTxTest() {
    userService2.insertWithoutTx();
}

運行結果:

拋出了異常,提示沒有存在的事務。

REQUIRES_NEW

這個理解起來可能會比較繞,官方的解釋是這樣子的:

Create a new transaction, and suspend the current transaction if one exists.

大意就是:重新創建一個新的事務,如果當前存在事務,延緩當前的事務。這個延緩,或者說掛起,可能理解起來比較難,下面通過例子來分析:

UserService.java

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insert() {
    UserEntity user = new UserEntity();
    user.setUsername("happyjava");
    user.setPassword("123456");
    userRepo.save(user);
}

這個insert方法的傳播行為改為REQUIRES_NEW。

UserService2.java

@Transactional
public void inserBatch() {
    UserEntity user = new UserEntity();
    user.setUsername("初次調用");
    user.setPassword("123456");
    userRepo.save(user);
    for (int i = 0; i < 10; i++) {
        if (i == 9) {
            throw new RuntimeException();
        }
        userService.insert();
    }
}

inserBatch擁有事務,然后后面循環調用的insert方法也有自己的事務。根據定義,inserBatch的事務會被延緩。具體表現就是:后面的10次循環的事務在每次循環結束之后都會提交自己的事務,而inserBatch的事務,要等循環方法走完之后再提交。但由於第10次循環會拋出異常,則inserBatch的事務會回滾,既數據庫中不會存在:“初次調用”的記錄:

測試代碼:

@Test
public void insertBatchTest() {
    userService2.inserBatch();
}

執行結果:

這種情況,符合開始說的“不要循環嵌套事務方法”的說話,當然是否需要循環嵌套,還是要看業務邏輯的。

NOT_SUPPORTED

Execute non-transactionally, suspend the current transaction if one exists.

以非事務的方式運行,如果當前存在事務,暫停當前的事務。這種方式與REQUIRES_NEW有所類似,但是NOT_SUPPORTED修飾的方法其本身是沒有事務的。這里就不做代碼演示了。

NEVER

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

@Transactional(propagation = Propagation.NEVER)
public void insert() {
    UserEntity user = new UserEntity();
    user.setUsername("happyjava");
    user.setPassword("123456");
    userRepo.save(user);
}

@Transactional
public void insertWithTx() {
    userService.insert();
}

執行結果:

NESTED

如果沒有事務,就新建一個事務;如果有,就在當前事務中嵌套其他事務。

這個也是理解起來比較費勁的一個行為。我們一步一步分析。

外圍方法沒有事務:這種情況跟REQUIRED是一樣的,會新建一個事務。

外圍方法如果存在事務:這種情況就會嵌套事務。所謂嵌套事務,大意就是,外圍事務回滾,內嵌事務一定回滾,而內嵌事務可以單獨回滾而不影響外圍主事務和其他子事務。

由於本人使用Spring Data JPA 進行的演示代碼,使用嵌套事務會提示:

org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider's capabilities

搜索了下,hibernate似乎不支持這種事務傳播方式。所以這里就不做演示了

總結

事務傳播行為,在開發中可能不會特別的留意到它(更多時候,我們可能只是使用默認的方式),但是還是需要對其要有所理解。希望本篇文章能讓大家明白Spring的7種事務傳播行為。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM