Spring事務(wù)傳播機(jī)制(最全示例)

我們在使用Spring框架進(jìn)行開發(fā)時,經(jīng)常在service層寫很多方法,而且這些方法都是帶事務(wù)的,那么Spring的事務(wù)怎么在多個方法之間傳播呢?今天我們就仔細(xì)聊一聊。

Spring的事務(wù)傳播機(jī)制主要解決在多個方法之間,事務(wù)如何傳遞的問題,通常有7種傳播類型:

  • REQUIRED

  • SUPPORTS

  • MANDATORY

  • REQUIRES_NEW

  • NOT_SUPPORTED

  • NEVER

  • NESTED

下面我們就一一演示這7種類型是如何工作的。

基礎(chǔ)代碼

在講解7種傳播類型之前,我們先看看基礎(chǔ)代碼,代碼很簡單,大家先熟悉一下:

public void outerTransaction() {
    //向表中插入文本“outerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("outerTransaction");
    transactionPropagationMapper.insert(tp);

    //調(diào)用innerTransaction方法
    TransactionPropagationService currentProxy = (TransactionPropagationService)AopContext.currentProxy();
    currentProxy.innerTransaction();

    //拋出異常
    int i = 1 / 0 ;
}


public void innerTransaction() {
    //向表中插入文本“innerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("innerTransaction");
    transactionPropagationMapper.insert(tp);

    //拋出異常
    int i = 1 / 0 ;
}

方法outerTransaction()向表中插入文本"outerTransaction",然后調(diào)用innerTransaction()方法,最后通過計算1 / 0拋出異常。

方法innerTransaction()向表中插入文本"innerTransaction",通過計算1 / 0拋出異常。

這里我們在調(diào)用innerTransaction()方法時,先獲取當(dāng)前的AOP代理,再通過代理調(diào)用。這是因為兩個方法在同一個類中,如果不通過代理,直接調(diào)用,會脫離Spring事務(wù)AOP的管理,導(dǎo)致事務(wù)失效。

我們在這兩個方法上使用注解,并配置不同的傳播機(jī)制,通過查看數(shù)據(jù)庫是否插入數(shù)據(jù)成功來演示不同傳播機(jī)制的效果。

REQUIRED

REQUIRED是Spring默認(rèn)的傳播機(jī)制,含義:如果當(dāng)前存在事務(wù),則加入該事務(wù),如果不存在事務(wù),則創(chuàng)建一個事務(wù)。下面我們分別演示一下:

  1. 如果不存在事務(wù),則創(chuàng)建一個事務(wù)。具體代碼如下:
public void outerTransaction() {
    //向表中插入文本“outerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("outerTransaction");
    transactionPropagationMapper.insert(tp);

    //調(diào)用innerTransaction方法
    TransactionPropagationService currentProxy = (TransactionPropagationService)AopContext.currentProxy();
    currentProxy.innerTransaction();
}


@Transactional(propagation = Propagation.REQUIRED)
public void innerTransaction() {
    //向表中插入文本“innerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("innerTransaction");
    transactionPropagationMapper.insert(tp);

    //拋出異常
    int i = 1 / 0 ;
}

outerTransaction()方法沒有事務(wù)注解,雖然調(diào)用innerTransaction()方法時有異常拋出,插入數(shù)據(jù)也應(yīng)該成功。innerTransaction()方法有事務(wù)注解,傳播方式為:REQUIRED,由于outerTransaction()沒有事務(wù),所以會新創(chuàng)建一個事務(wù),后面有異常拋出,所以數(shù)據(jù)不會插入成功,我們測試一下,看看結(jié)果如何?

image-20240924121028544-1727151045051-1.png

和我們的預(yù)期是一致的,innerTransaction()創(chuàng)建了新的事務(wù),由于拋出異常,所以數(shù)據(jù)沒有插入成功。

  1. 如果當(dāng)前存在事務(wù),則加入該事務(wù),代碼如下:
@Transactional(propagation = Propagation.REQUIRED)
public void outerTransaction() {
    //向表中插入文本“outerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("outerTransaction");
    transactionPropagationMapper.insert(tp);

    //調(diào)用innerTransaction方法
    TransactionPropagationService currentProxy = (TransactionPropagationService)AopContext.currentProxy();
    currentProxy.innerTransaction();
    
    //拋出異常
    int i = 1 / 0 ;
}


@Transactional(propagation = Propagation.REQUIRED)
public void innerTransaction() {
    //向表中插入文本“innerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("innerTransaction");
    transactionPropagationMapper.insert(tp);
}

outerTransaction()增加了事務(wù)注解,傳播類型為REQUIRED,由于之前沒有事務(wù),所以新創(chuàng)建了一個事務(wù),然后調(diào)用innerTransaction(),innerTransaction()的傳播類型也為REQUIRED,由于前面有事務(wù),所以加入事務(wù),最后outerTransaction()拋出異常,由于兩個方法在同一個事務(wù)中,所以兩個數(shù)據(jù)都不會插入成功。我們測試一下,

image-20240924122731048.png

和我們的預(yù)期是一致的,innerTransaction()加入了outerTransaction()的事務(wù),拋出異常后,兩條數(shù)據(jù)都不會插入成功。

SUPPORTS

如果當(dāng)前存在事務(wù),則加入該事務(wù),如果不存在事務(wù),則以非事務(wù)的方式執(zhí)行。同樣我們分別演示一下。

  1. 如果當(dāng)前存在事務(wù),則加入該事務(wù),代碼如下:
@Transactional(propagation = Propagation.REQUIRED)
public void outerTransaction() {
    //向表中插入文本“outerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("outerTransaction");
    transactionPropagationMapper.insert(tp);

    //調(diào)用innerTransaction方法
    TransactionPropagationService currentProxy = (TransactionPropagationService)AopContext.currentProxy();
    currentProxy.innerTransaction();

    //拋出異常
    int i = 1 / 0 ;
}


@Transactional(propagation = Propagation.SUPPORTS)
public void innerTransaction() {
    //向表中插入文本“innerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("innerTransaction");
    transactionPropagationMapper.insert(tp);

}

outerTransaction()是有事務(wù)的,innerTransaction()的傳播類型為:SUPPORTS,則會加入到事務(wù)中,由于兩個方法在同一個事務(wù)中,拋出異常后,兩條數(shù)據(jù)都不會插入成功,我們測試一下,

image-20240924123357342-1727152445217-3.png

和預(yù)期一致,沒有問題。

  1. 如果不存在事務(wù),則以非事務(wù)的方式執(zhí)行,具體代碼如下:
public void outerTransaction() {
    //向表中插入文本“outerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("outerTransaction");
    transactionPropagationMapper.insert(tp);

    //調(diào)用innerTransaction方法
    TransactionPropagationService currentProxy = (TransactionPropagationService)AopContext.currentProxy();
    currentProxy.innerTransaction();
}


@Transactional(propagation = Propagation.SUPPORTS)
public void innerTransaction() {
    //向表中插入文本“innerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("innerTransaction");
    transactionPropagationMapper.insert(tp);
    
    //拋出異常
    int i = 1 / 0 ;
}

我們將outerTransaction()方法的事務(wù)注解去掉,拋出異常的位置挪到innerTransaction()中,由于innerTransaction()的傳播類型是SUPPORTS,外層是沒有事務(wù)的,所以innerTransaction()也是沒有事務(wù)的,雖然拋出了異常,但是不會回滾,兩條數(shù)據(jù)都應(yīng)該插入成功,我們測試一下,

image-20240924125916177-1727153959454-5.png

和預(yù)期一致,沒有問題。

MANDATORY

如果當(dāng)前存在事務(wù),則加入到事務(wù)當(dāng)中;如果當(dāng)前沒有事務(wù),則拋出異常。我們分別演示一下,

  1. 如果當(dāng)前存在事務(wù),則加入到事務(wù)當(dāng)中,代碼如下:
@Transactional(propagation = Propagation.REQUIRED)
public void outerTransaction() {
    //向表中插入文本“outerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("outerTransaction");
    transactionPropagationMapper.insert(tp);

    //調(diào)用innerTransaction方法
    TransactionPropagationService currentProxy = (TransactionPropagationService)AopContext.currentProxy();
    currentProxy.innerTransaction();

    //拋出異常
    int i = 1 / 0 ;
}


@Transactional(propagation = Propagation.MANDATORY)
public void innerTransaction() {
    //向表中插入文本“innerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("innerTransaction");
    transactionPropagationMapper.insert(tp);
}

outerTransaction()方法是有事務(wù)的,innerTransaction()方法的傳播類型是MANDATORY,會加入到事務(wù)中,由于outerTransaction()方法拋出了異常,所以兩條數(shù)據(jù)都不會成功,我們測試一下,

image-20240924130447464-1727154298756-7.png

和預(yù)期一致,都沒有成功。

  1. 如果當(dāng)前沒有事務(wù),則拋出異常,代碼如下:
public void outerTransaction() {
    //向表中插入文本“outerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("outerTransaction");
    transactionPropagationMapper.insert(tp);

    //調(diào)用innerTransaction方法
    TransactionPropagationService currentProxy = (TransactionPropagationService)AopContext.currentProxy();
    currentProxy.innerTransaction();

    //拋出異常
    int i = 1 / 0 ;
}


@Transactional(propagation = Propagation.MANDATORY)
public void innerTransaction() {
    //向表中插入文本“innerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("innerTransaction");
    transactionPropagationMapper.insert(tp);
}

我們只是去掉了outerTransaction()上的事務(wù)注解,我們看一下會不會拋出異常,測試一下,

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'

確實(shí)是拋出了異常,我們再查看一下數(shù)據(jù)庫的數(shù)據(jù),


image-20240924130839780.png

outerTransaction()方法的數(shù)據(jù)插入成功了,因為outerTransaction()方法沒有事務(wù),雖然后面的方法拋出了異常,但數(shù)據(jù)還是會插入成功。

REQUIRES_NEW

總是創(chuàng)建一個新的事務(wù),如果當(dāng)前存在事務(wù),則掛起當(dāng)前事務(wù)。這句話怎么理解呢?我們看看下面的代碼,

@Transactional(propagation = Propagation.REQUIRED)
public void outerTransaction() {
    //向表中插入文本“outerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("outerTransaction");
    transactionPropagationMapper.insert(tp);

    //調(diào)用innerTransaction方法
    TransactionPropagationService currentProxy = (TransactionPropagationService)AopContext.currentProxy();
    currentProxy.innerTransaction();

    //拋出異常
    int i = 1 / 0 ;
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void innerTransaction() {
    //向表中插入文本“innerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("innerTransaction");
    transactionPropagationMapper.insert(tp);
}

outerTransaction()是有事務(wù)的,innerTransaction()方法的傳播類型是REQUIRES_NEW,會創(chuàng)建一個新的事務(wù),雖然outerTransaction()最后拋出了異常,由于兩個方法是兩個事務(wù),所以異常只會對外層事務(wù)回滾,我們測試一下,

image-20240924131729385.png

innerTransaction()插入數(shù)據(jù)成功,outerTransaction()方法由于有異常,所以進(jìn)行了回滾。如果將異常從外層挪到內(nèi)層,也就是外層不拋出異常,而內(nèi)層拋出異常,執(zhí)行結(jié)果會是什么樣子呢?小伙伴們自己思考一下吧。

NOT_SUPPORTED

以非事務(wù)的方式執(zhí)行操作,如果當(dāng)前存在事務(wù),則掛起當(dāng)前事務(wù)。這種傳播類型說明方法都是非事務(wù)的,不管外層有沒有事務(wù),我們先看看代碼,

@Transactional(propagation = Propagation.REQUIRED)
public void outerTransaction() {
    //向表中插入文本“outerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("outerTransaction");
    transactionPropagationMapper.insert(tp);

    //調(diào)用innerTransaction方法
    TransactionPropagationService currentProxy = (TransactionPropagationService)AopContext.currentProxy();
    currentProxy.innerTransaction();
}

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void innerTransaction() {
    //向表中插入文本“innerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("innerTransaction");
    transactionPropagationMapper.insert(tp);
    
    //拋出異常
    int i = 1 / 0 ;
}

outerTransaction()是有事務(wù)的,innerTransaction()的傳播類型是NOT_SUPPORTED,說明innerTransaction()以非事務(wù)執(zhí)行,數(shù)據(jù)插入后,拋出異常,由于外層是有事務(wù)的,所以外層事務(wù)回滾,我們測試一下,

image-20240924132822937.png

和預(yù)期是一致的,內(nèi)層以非事務(wù)執(zhí)行,插入數(shù)據(jù)成功,外層有事務(wù),而且有異常,所以事務(wù)回滾。

NEVER

以非事務(wù)的方式執(zhí)行操作,如果當(dāng)前存在事務(wù),則拋出異常。我們具體看一下代碼,

@Transactional(propagation = Propagation.REQUIRED)
public void outerTransaction() {
    //向表中插入文本“outerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("outerTransaction");
    transactionPropagationMapper.insert(tp);

    //調(diào)用innerTransaction方法
    TransactionPropagationService currentProxy = (TransactionPropagationService)AopContext.currentProxy();
    currentProxy.innerTransaction();
}

@Transactional(propagation = Propagation.NEVER)
public void innerTransaction() {
    //向表中插入文本“innerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("innerTransaction");
    transactionPropagationMapper.insert(tp);

    //拋出異常
    int i = 1 / 0 ;
}

outerTransaction()有事務(wù),innerTransaction() 的傳播類型是NEVER,由于外層方法有事務(wù),所以要拋異常,外層方法也要回滾,所以兩條數(shù)據(jù)都不會插入成功,我們測試一下,

拋出的異常是:

org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'

再看看數(shù)據(jù)庫中的數(shù)據(jù),


image-20240924133329275.png

和預(yù)期是一致的。

NESTED

如果當(dāng)前存在事務(wù),則在當(dāng)前事務(wù)中創(chuàng)建一個新的嵌套事務(wù);如果當(dāng)前沒有事務(wù),則創(chuàng)建一個新的任務(wù)。我們分別看一下是什么意思。

  1. 如果當(dāng)前沒有事務(wù),則創(chuàng)建一個新的任務(wù)。這個感覺和REQUIRED是一樣的,我們先看看代碼,
public void outerTransaction() {
    //向表中插入文本“outerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("outerTransaction");
    transactionPropagationMapper.insert(tp);

    //調(diào)用innerTransaction方法
    TransactionPropagationService currentProxy = (TransactionPropagationService)AopContext.currentProxy();
    currentProxy.innerTransaction();
}

@Transactional(propagation = Propagation.NESTED)
public void innerTransaction() {
    //向表中插入文本“innerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("innerTransaction");
    transactionPropagationMapper.insert(tp);

    //拋出異常
    int i = 1 / 0 ;
}

outerTransaction()沒有事務(wù),innerTransaction()的傳播類型是NESTED,會創(chuàng)建一個新的事務(wù),由于拋出了異常所以內(nèi)層會回滾,外層沒有事務(wù),會插入數(shù)據(jù)成功,我們測試一下,

image-20240924134045654.png

和預(yù)期一致。

  1. 如果當(dāng)前存在事務(wù),則在當(dāng)前事務(wù)中創(chuàng)建一個新的嵌套事務(wù)。我們再看看代碼,
@Transactional(propagation = Propagation.REQUIRED)
public void outerTransaction() {
    //向表中插入文本“outerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("outerTransaction");
    transactionPropagationMapper.insert(tp);

    //調(diào)用innerTransaction方法
    TransactionPropagationService currentProxy = (TransactionPropagationService)AopContext.currentProxy();
    try {
        currentProxy.innerTransaction1();
        currentProxy.innerTransaction2();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

@Transactional(propagation = Propagation.NESTED)
public void innerTransaction1() {
    //向表中插入文本“innerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("innerTransaction1");
    transactionPropagationMapper.insert(tp);
}

@Transactional(propagation = Propagation.NESTED)
public void innerTransaction2() {
    //向表中插入文本“innerTransaction”
    TransactionPropagation tp = new TransactionPropagation();
    tp.setMethodName("innerTransaction2");
    transactionPropagationMapper.insert(tp);

    //拋出異常
    int i = 1 / 0 ;
}

outerTransaction()有事務(wù),分別調(diào)用innerTransaction1()和innerTransaction2() ,并catch異常,innerTransaction1()和innerTransaction2()的傳播機(jī)制都是NESTED,會分別創(chuàng)建一個內(nèi)嵌事務(wù),innerTransaction1()正常結(jié)束,沒有異常,innerTransaction2()拋出異常事務(wù)回滾,而外層由于catch了異常,方法也可以正常結(jié)束,所以不會回滾。我們預(yù)測的是:outerTransaction()插入成功,innerTransaction1()插入成功,innerTransaction2()回滾。我們測試一下,

image-20240924135549273.png

和我們的預(yù)測是一致的。

總結(jié)

到此,Spring的7種傳播機(jī)制就介紹完了。這里邊的內(nèi)容很多,是不好記憶的,其實(shí)我們也不必死記硬背,看看源碼中的注釋就可以了。如果再不行,就翻翻我的博客多看看吧~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容