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

前言

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

Spring中,有7種類型的事務傳播行爲。事務傳播行爲是Spring框架提供的一種事務管理方式,它不是數據庫提供的。不知道你們是否據說過「不要在service事務方法中嵌套事務方法,這樣會提交多個事務」的說法,其實這是不許確的。瞭解了事務傳播行爲以後,相信你就會明白!mysql

原創聲明

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

Spring中七種事務傳播行爲

事務的傳播行爲,默認值爲 Propagation.REQUIRED。能夠手動指定其餘的事務傳播行爲,以下:sql

  • Propagation.REQUIRED

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

  • Propagation.SUPPORTS

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

  • Propagation.MANDATORY

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

  • Propagation.REQUIRES_NEW

從新建立一個新的事務,若是當前存在事務,延緩當前的事務。ide

  • Propagation.NOT_SUPPORTED

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

  • Propagation.NEVER

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

  • 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種事務傳播行爲。

相關文章
相關標籤/搜索