事務(wù)、@Transactional失效場景

[TOC]

參考鏈接:
MySql的四種事務(wù)隔離級別 - 超級小小黑 - 博客園
一口氣說出 6種 @Transactional 注解失效場景

一、事務(wù)

事務(wù)的特性:
A(atomicity):原子性,一個事務(wù)時一個不可分割的工作單位,要么都提交,要不都不提交。
C(consistency):一致性,事務(wù)前后,數(shù)據(jù)狀態(tài)一致。^ 一致性例子
I(isolation):隔離性,事務(wù)互不干擾。
D(durability):持久性,一旦提交,數(shù)據(jù)永久性更改。

編程式事務(wù)

在代碼中手動提交事務(wù)、回滾等操作,代碼侵入性比較強

try {
    //TODO something
     transactionManager.commit(status);
} catch (Exception e) {
    transactionManager.rollback(status);
    throw new InvoiceApplyException("異常失敗");
}

聲明式事務(wù)

基于AOP面向切面,將業(yè)務(wù)與事務(wù)處理解耦,代碼侵入性很低,所以是最常用的事務(wù)管理方式。

聲明式事務(wù)實現(xiàn)方式有兩種,一種基于TX和AOP的xml配置文件方式,第二種就是基于@Transactional注解。

@Transactional
@GetMapping("/test")
public String test() {
    int insert = cityInfoDictMapper.insert(cityInfoDict);
}

二、@Transactional介紹

1. @Transactional注解作用于哪些地方。

@Transactional作用于接口,方法。

  • 作用于類:
    表示該類的public方法都配置相同的事務(wù)屬性信息。
  • 作用于方法:
    類配置了@Transactional,該類下方法也配置了@Transactional,方法的事務(wù)會覆蓋類的事務(wù)。
  • 作用于接口:
    不推薦這種方法,因為一旦標注在Interface上并且配置了Spring AOP使用CGlib動態(tài)代理,將會導致@Transactional注解會失效。
@Transactional
@RestController
@RequestMapping
public class MybatisPlusController {
    @Autowired
    private CityInfoDictMapper cityInfoDictMapper;

    @Transactional(rollbackFor = Exception.class)
    @GetMapping("/test")
    public String test() throws Exception {
        return "success";
    }
}

2.@Transactional注解屬性

  • propagation

    propagation代表事務(wù)的傳播屬性,

    Propagation.REQUIRED:(默認)如果當前存在事務(wù),則加入該事務(wù),如果當前不存在事務(wù),則創(chuàng)建一個新的事務(wù)。(也就是說如果A方法和B方法都添加了注解,在默認傳播模式下,A方法內(nèi)部調(diào)用B方法,會把兩個方法的事務(wù)合并為一個事務(wù))

    Propagation.SUPPORT:如果當前存在事務(wù),則加入該事務(wù);如果當前不存在事務(wù),則以非事務(wù)的方式繼續(xù)運行。

    Propagation.MADATORY:如果當前存在事務(wù),則加入該事務(wù);如果當前不存在事務(wù),則拋出異常。

    Propagation.REQUIRES_NEW:重新創(chuàng)建一個新的事務(wù),如果當前存在事務(wù),暫停當前的事務(wù)。(當類A中的a方法用默認Propagation.REQUIRED模式,類B中的b方法上采用Propagation.REQUIRES_NEW模式,然后在a方法中調(diào)用b方法操作數(shù)據(jù)庫,然而a方法拋出異常,b方法并沒有進行回滾,因為Propagation.REQUIRES_NEW會暫停a方法的事務(wù))

    Propagation.NOT_SUPPORTED:以非事務(wù)的方式運行,如果當前存在事務(wù),暫停當前的事務(wù)。

    Propagetion.NEVER:以非事務(wù)的方式運行,如果當前存在事務(wù),則拋出異常。

    Propagetion.NESTED:和Propagetion.REQUIRED效果一樣。

  • isolation

    isalation事務(wù)的隔離級別

    Isolation.DEFAULT:(默認)使用底層數(shù)據(jù)庫默認的隔離級別。大多數(shù)數(shù)據(jù)庫默認的事務(wù)隔離級別是Read committed,比如Sql Server,Oracle。Mysql的默認隔離級別是Repeatable read。

    Isolation.READ_UNCOMMITTED
    允許臟讀,但不允許更新丟失。如果一個事務(wù)已經(jīng)開始寫數(shù)據(jù),則另外一個事務(wù)則不允許同時進行寫操作,但允許其他事務(wù)讀此行數(shù)據(jù)。該隔離級別可以通過”排他寫鎖“實現(xiàn)。

    Isolation.READ_COMMITTED
    允許不可重復讀取,但不允許臟讀取。這可以通過“瞬間共享讀鎖”和“排他寫鎖”實現(xiàn)。讀取數(shù)據(jù)的事務(wù)允許其他事務(wù)繼續(xù)訪問該行數(shù)據(jù),但是未提交的寫事務(wù)將會禁止其他事務(wù)訪問該行。

    Isolation.REPEATABLE_READ
    禁止不可重復讀取和臟讀取,但不允許臟讀取。這可以通過“瞬間共享讀鎖”和“排他寫鎖”實現(xiàn)。讀取數(shù)據(jù)的事務(wù)允許其他事務(wù)繼續(xù)訪問該行數(shù)據(jù),但是未提交的寫事務(wù)將會禁止其他事務(wù)訪問該行。

    Isolation.SERIALIZABLE
    它要求事務(wù)序列化執(zhí)行,事務(wù)只能一個接著一個的執(zhí)行,不能并發(fā)執(zhí)行。僅僅通過“行級鎖”是無法實現(xiàn)事務(wù)序列化的,必須通過其他機制保證新插入的數(shù)據(jù)不會被剛執(zhí)行查詢操作的事務(wù)訪問到。

    讀數(shù)據(jù)一致性 臟讀 不可重復讀 幻讀
    未提交讀 最低級別只能保證不讀取物理上損壞的數(shù)據(jù) :heavy_check_mark: :heavy_check_mark: :heavy_check_mark:
    已提交讀 語句級 :x: :heavy_check_mark: :heavy_check_mark:
    可重復讀 事務(wù)級 :x: :x: :heavy_check_mark:
    序列化 最高級別,事務(wù)級 :x: :x: :x:
    1. 臟讀
      又稱無效數(shù)據(jù)讀出。一個事務(wù)讀取另外一個事務(wù)還沒有提交的數(shù)據(jù)叫臟讀。
      例如:事務(wù)T1修改了一行數(shù)據(jù),但是還沒有提交,這時候事務(wù)T2讀取了被事務(wù)T1修改后的數(shù)據(jù),之后事務(wù)T1因為某種原因Rollback了,那么事務(wù)T2讀取的就是臟數(shù)據(jù)。

    2. 不可重復讀
      同一個事務(wù)中,多次讀出的同一數(shù)據(jù)是不一致的。
      例如:事務(wù)T1讀取某一數(shù)據(jù),事務(wù)T2讀取并修改了該數(shù)據(jù),T1為了對讀取值進行檢驗而再次讀取該數(shù)據(jù),便得到了不同的結(jié)果。

    3. 幻讀
      不好表述直接上例子吧:

      在倉庫管理中,管理員要給剛到的一批商品進行入庫管理,當然入庫之前肯定是要查一下之前有沒有入庫記錄,確保正確性。管理員A確保庫中不存在該商品之后給該商品進行入庫操作,假如這時管理員B因為手快將該商品進行了入庫操作。這時管理員A發(fā)現(xiàn)該商品已經(jīng)在庫中。就像剛剛發(fā)生了幻讀一樣,本來不存在的東西,突然之間他就有了。

      注:三種問題看似不太好理解,臟讀側(cè)重的是數(shù)據(jù)的正確性。不可重復讀側(cè)重的是對數(shù)據(jù)的修改,幻讀側(cè)重于數(shù)據(jù)的新增和刪除。

  • timeout

    timeout事務(wù)的超時時間。
    默認值為-1。如果超過該時間限制但事務(wù)還沒有完成,則自動回滾事務(wù)。

  • readOnly

    readOnly:指定事務(wù)是否為只讀事務(wù)。
    默認值為false;為了忽略那些不需要事務(wù)的方法,比如讀取數(shù)據(jù),可以設(shè)置成read-only為true

  • rellbackFor

    rollbackFor用于指定能夠觸發(fā)事務(wù)回滾的異常類型,可以指定多個異常類型。

  • noRollbackFor

    noRollbackFor:拋出指定的異常類型,不回滾事務(wù),也可以指定多個異常類型。

三、@Transactional失效場景

1.@Transactional應(yīng)用在非public方法上

編譯不會報錯,但是自己要注意

2.@Transactional屬性propagetion設(shè)置錯誤

若配置以下三種propagation,事務(wù)有可能不回滾。

Propagation.SUPPORT^ Propagation.SUPPORT

Propagation.NOT_SUPPORTED[^ Propagation.NOT_SUPPORTED]

Propagetion.NEVER[1]

3.@Transactional注解rollbackbakcFor設(shè)置錯誤

4.同一個類中方法調(diào)用,導致@Transactional失效

開發(fā)中避免不了會對同一個類里面的方法調(diào)用,比如有一個類Test,它的一個方法A,A再調(diào)用本類的方法B(不論方法B是用public還是private修飾),但方法A沒有聲明注解事務(wù),而B方法有。則外部調(diào)用方法A之后,方法B的事務(wù)還是不起作用的。這也是經(jīng)常犯錯誤的一個地方。

public class Test{

    @GetMapping("/A")
    public String A(){
        String result = this.B();
        return result;
    }
    @GetMapping("/B")
    @Transactional(rollbackFor=Exception.class)
    public String B(){
        String result = "1";
        result+="2";
        throw new Exception("模擬報錯..");
        result+="3";
        return result;
    }
}



/*訪問/A,調(diào)用的B的事務(wù)不起作用,訪問/B,B的事務(wù)起作用*/

為啥會出現(xiàn)這種情況?
其實著還是由于使用Spring AOP代理造成的,因為只有當事務(wù)方法被當前類以外的代碼調(diào)用時,才會由Spring生成代理對象來管理。

5.異常被catch,沒有拋出給Spring

這種情況最常見!

@Transactional
private Integer A() throws Exception {
    int insert = 0;
    try {
        CityInfoDict cityInfoDict = new CityInfoDict();
        cityInfoDict.setCityName("2");
        cityInfoDict.setParentCityId(2);
        /**
         * A 插入字段為 2的數(shù)據(jù)
         */
        insert = cityInfoDictMapper.insert(cityInfoDict);
        /**
         * B 插入字段為 3的數(shù)據(jù)
         */
        b.insertB();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

如果B方法內(nèi)部拋了異常,而A方法此時try catch了B方法的異常,那這個事務(wù)還能正常回滾嗎?

答案:不能!

因為當ServiceB中拋出了一個異常以后,ServiceB標識當前事務(wù)需要rollback。但是ServiceA中由于你手動的捕獲這個異常并進行處理,ServiceA認為當前事務(wù)應(yīng)該正常commit。此時就出現(xiàn)了前后不一致,也就是因為這樣,拋出了前面的UnexpectedRollbackException異常。

spring的事務(wù)實在調(diào)用業(yè)務(wù)方法之前開始的,業(yè)務(wù)方法執(zhí)行完畢之后才執(zhí)行commitorrollback,事務(wù)是否執(zhí)行取決于是否拋出runtime異常。如果拋出runtime exception并在你的業(yè)務(wù)方法中沒有catch到的話,事務(wù)會回滾。

在業(yè)務(wù)方法中一般不需要catch異常,如果非要catch,一定要拋出throw new RuntimeException(),或者注解中指定拋異常類型@Transactional(rollbackFor=Exception),否則事務(wù)失效。

6.數(shù)據(jù)庫引擎不支持事務(wù)

mysql默認使用innodb引擎,所以基本不會出現(xiàn)這個問題,關(guān)于數(shù)據(jù)庫引擎,都使用innodb即可。


  1. 以非事務(wù)的方式運行,如果當前存在事務(wù),則拋出異常。
    [^ Propagation.NOT_SUPPORTED]: 以非事務(wù)的方式運行,如果當前存在事務(wù),暫停當前的事務(wù) ?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容