最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

注解@Transactional原理分析以及常見的坑

 更新時間:2025年10月23日 10:42:24   作者:吳名氏.  
這篇文章主要介紹了注解@Transactional原理分析以及常見的坑,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

這篇文章,會先講述 @Transactional 的 4 種不生效的 Case,然后再通過源碼解讀,分析 @Transactional 的執(zhí)行原理,以及部分 Case 不生效的真正原因

1項目準(zhǔn)備

下面是 DB 數(shù)據(jù)和 DB 操作接口:

uid

uname

usex

1

張三

2

陳恒

3

樓仔

// 提供的接口
public interface UserDao {
    // select * from user_test where uid = "#{uid}"
    public MyUser selectUserById(Integer uid);
    // update user_test set uname =#{uname},usex = #{usex} where uid = #{uid}
    public int updateUser(MyUser user);
}

基礎(chǔ)測試代碼,testSuccess() 是事務(wù)生效的情況:

@Service
public class UserController {
    @Autowired
    private UserDao userDao;

    public void update(Integer id) {
        MyUser user = new MyUser();
        user.setUid(id);
        user.setUname("張三-testing");
        user.setUsex("女");
        userDao.updateUser(user);
    }

    public MyUser query(Integer id) {
        MyUser user = userDao.selectUserById(id);
        return user;
    }

    // 正常情況
    @Transactional(rollbackFor = Exception.class)
    public void testSuccess() throws Exception {
        Integer id = 1;
        MyUser user = query(id);
        System.out.println("原記錄:" + user);
        update(id);
        throw new Exception("事務(wù)生效");
    }
}

2事務(wù)不生效的幾種 Case

主要講解 4 種事務(wù)不生效的 Case:

  • 類內(nèi)部訪問:A 類的 a1 方法沒有標(biāo)注 @Transactional,a2 方法標(biāo)注 @Transactional,在 a1 里面調(diào)用 a2;
  • 私有方法:將 @Transactional 注解標(biāo)注在非 public 方法上;
  • 異常不匹配:@Transactional 未設(shè)置 rollbackFor 屬性,方法返回 Exception 等異常;
  • 多線程:主線程和子線程的調(diào)用,線程拋出異常。

2.1 類內(nèi)部訪問會導(dǎo)致事務(wù)不生效

我們在類 UserController 中新增一個方法 testInteralCall():

public void testInteralCall() throws Exception {
    testSuccess();
    throw new Exception("事務(wù)不生效:類內(nèi)部訪問");
}

這里 testInteralCall() 沒有標(biāo)注 @Transactional,我們再看一下測試用例:

public static void main(String[] args) throws Exception {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserController uc = (UserController) applicationContext.getBean("userController");
    try {
        uc.testInteralCall();
    } finally {
        MyUser user =  uc.query(1);
        System.out.println("修改后的記錄:" + user);
    }
}
// 輸出:
// 原記錄:MyUser(uid=1, uname=張三, usex=女)
// 修改后的記錄:MyUser(uid=1, uname=張三-testing, usex=女)

從上面的輸出可以看到,事務(wù)并沒有回滾,這個是什么原因呢?

因為 @Transactional 的工作機(jī)制是基于 AOP 實現(xiàn),AOP 是使用動態(tài)代理實現(xiàn)的,如果通過代理直接調(diào)用 testSuccess(),通過 AOP 會前后進(jìn)行增強(qiáng),增強(qiáng)的邏輯其實就是在 testSuccess() 的前后分別加上開啟、提交事務(wù)的邏輯,后面的源碼會進(jìn)行剖析。

現(xiàn)在是通過 testInteralCall() 去調(diào)用 testSuccess(),testSuccess() 前后不會進(jìn)行任何增強(qiáng)操作,也就是類內(nèi)部調(diào)用,不會通過代理方式訪問。

2.2 私有方法也會導(dǎo)致事務(wù)失效

在私有方法上,添加 @Transactional 注解也不會生效:

@Transactional(rollbackFor = Exception.class)
private void testPirvateMethod() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原記錄:" + user);
    update(id);
    throw new Exception("測試事務(wù)生效");
}

直接使用時,下面這種場景不太容易出現(xiàn),因為 IDEA 會有提醒,文案為: Methods annotated with '@Transactional' must be overridable,至于深層次的原理,源碼部分會進(jìn)行解讀。

2.3 異常不匹配也會導(dǎo)致事務(wù)失效

這里的 @Transactional 沒有設(shè)置 rollbackFor = Exception.class 屬性:

@Transactional
public void testExceptionNotMatch() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原記錄:" + user);
    update(id);
    throw new Exception("事務(wù)不生效:異常不匹配");
}
public static void main(String[] args) throws Exception {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserController uc = (UserController) applicationContext.getBean("userController");
    try {
        uc.testSuccess();
    } finally {
        MyUser user =  uc.query(1);
        System.out.println("修改后的記錄:" + user);
    }
}

// 輸出:
// 原記錄:User[uid=1,uname=張三,usex=女]
// 修改后的記錄:User[uid=1,uname=張三-test,usex=女]

@Transactional 注解默認(rèn)處理運(yùn)行時異常,即只有拋出運(yùn)行時異常時,才會觸發(fā)事務(wù)回滾,否則并不會回滾,至于深層次的原理,源碼部分會進(jìn)行解讀。

2.4 多線程也會導(dǎo)致事務(wù)失效

下面給出兩個不同的姿勢,一個是子線程拋異常,主線程 ok;一個是子線程 ok,主線程拋異常。

父線程拋出異常

父線程拋出異常,子線程不拋出異常:

public void testSuccess() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原記錄:" + user);
    update(id);
}
@Transactional(rollbackFor = Exception.class)
public void testMultThread() throws Exception {
    new Thread(new Runnable() {
        @SneakyThrows
        @Override
        public void run() {
            testSuccess();
        }
    }).start();
    throw new Exception("測試事務(wù)不生效");
}

父線程拋出線程,事務(wù)回滾,因為子線程是獨立存在,和父線程不在同一個事務(wù)中,所以子線程的修改并不會被回滾,

子線程拋出異常

父線程不拋出異常,子線程拋出異常:

public void testSuccess() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原記錄:" + user);
    update(id);
    throw new Exception("測試事務(wù)不生效");
}
@Transactional(rollbackFor = Exception.class)
public void testMultThread() throws Exception {
    new Thread(new Runnable() {
        @SneakyThrows
        @Override
        public void run() {
            testSuccess();
        }
    }).start();
}

由于子線程的異常不會被外部的線程捕獲,所以父線程不拋異常,事務(wù)回滾沒有生效。

3 源碼解讀

下面我們從源碼的角度,對 @Transactional 的執(zhí)行機(jī)制和事務(wù)不生效的原因進(jìn)行解讀。

3.1 @Transactional 執(zhí)行機(jī)制

我們只看最核心的邏輯,代碼中的 interceptorOrInterceptionAdvice 就是 TransactionInterceptor 的實例,入?yún)⑹?this 對象。

紅色方框有一段注釋,大致翻譯為 “它是一個攔截器,所以我們只需調(diào)用即可:在構(gòu)造此對象之前,將靜態(tài)地計算切入點。”

this 是 ReflectiveMethodInvocation 對象,成員對象包含 UserController 類、testSuccess() 方法、入?yún)⒑痛韺ο蟮取?/p>

進(jìn)入 invoke() 方法后:

前方高能?。?!這里就是事務(wù)的核心邏輯,包括判斷事務(wù)是否開啟、目標(biāo)方法執(zhí)行、事務(wù)回滾、事務(wù)提交。

3.2 private 導(dǎo)致事務(wù)不生效原因

在上面這幅圖中,第一個紅框區(qū)域調(diào)用了方法 getTransactionAttribute(),主要是為了獲取 txAttr 變量,它是用于讀取 @Transactional 的配置,如果這個 txAttr = null,后面就不會走事務(wù)邏輯,我們看一下這個變量的含義:

我們直接進(jìn)入 getTransactionAttribute(),重點關(guān)注獲取事務(wù)配置的方法。

前方高能?。?!這里就是 private 導(dǎo)致事務(wù)不生效的原因所在,allowPublicMethodsOnly() 一直返回 false,所以重點只關(guān)注 isPublic() 方法。

下面通過位與計算,判斷是否為 Public,對應(yīng)的幾類修飾符如下:

  • PUBLIC: 1
  • PRIVATE: 2
  • PROTECTED: 4

看到這里,是不是豁然開朗了,有沒有覺得很有意思呢~~

3.3 異常不匹配原因

我們繼續(xù)回到事務(wù)的核心邏輯,因為主方法拋出 Exception() 異常,進(jìn)入事務(wù)回滾的邏輯:

進(jìn)入 rollbackOn() 方法,判斷該異常是否能進(jìn)行回滾,這個需要判斷主方法拋出的 Exception() 異常,是否在 @Transactional 的配置中:

我們進(jìn)入 getDepth() 看一下異常規(guī)則匹配邏輯,因為我們對 @Transactional 配置了 rollbackFor = Exception.class,所以能匹配成功:

示例中的 winner 不為 null,所以會跳過下面的環(huán)節(jié)。但是當(dāng) winner = null 時,也就是沒有設(shè)置 rollbackFor 屬性時,會走默認(rèn)的異常捕獲方式。

前方高能?。?!這里就是異常不匹配原因的原因所在,我們看一下默認(rèn)的異常捕獲方式:

是不是豁然開朗,當(dāng)沒有設(shè)置 rollbackFor 屬性時,默認(rèn)只對 RuntimeException 和 Error 的異常執(zhí)行回滾。

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 解決報錯:java:讀取jar包時出錯:error in opening zip file問題

    解決報錯:java:讀取jar包時出錯:error in opening zip 

    文章總結(jié):解決Java讀取jar包時出錯的問題,通過下載源碼并刷新項目解決了問題,希望對大家有所幫助
    2024-11-11
  • SpringBoot RedisTemplate分布式鎖的項目實戰(zhàn)

    SpringBoot RedisTemplate分布式鎖的項目實戰(zhàn)

    本文主要介紹了SpringBoot RedisTemplate分布式鎖的項目實戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • Feign?日期格式轉(zhuǎn)換錯誤的問題

    Feign?日期格式轉(zhuǎn)換錯誤的問題

    這篇文章主要介紹了Feign?日期格式轉(zhuǎn)換錯誤的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • java一個接口多個實現(xiàn)類的調(diào)用方式

    java一個接口多個實現(xiàn)類的調(diào)用方式

    這篇文章主要給大家介紹了關(guān)于java一個接口多個實現(xiàn)類的調(diào)用方式的相關(guān)資料,經(jīng)測試確認(rèn),當(dāng)一個接口有多個實現(xiàn)時,調(diào)用時只會執(zhí)行一個,有時候需要多個實現(xiàn)調(diào)用,需要的朋友可以參考下
    2023-09-09
  • 淺析SpringBoot統(tǒng)一返回結(jié)果的實現(xiàn)

    淺析SpringBoot統(tǒng)一返回結(jié)果的實現(xiàn)

    前后端開發(fā)過程中數(shù)據(jù)交互規(guī)范化是一件非常重要的事情,不僅可以減少前后端交互過程中出現(xiàn)的問題,也讓代碼邏輯更加具有條理,下面小編就和大家講講SpringBoot如何統(tǒng)一返回結(jié)果的吧
    2023-07-07
  • spring-boot-maven-plugin引入出現(xiàn)爆紅(已解決)

    spring-boot-maven-plugin引入出現(xiàn)爆紅(已解決)

    這篇文章主要介紹了spring-boot-maven-plugin引入出現(xiàn)爆紅(已解決),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • Java中的包、抽象類、接口詳解(最新整理)

    Java中的包、抽象類、接口詳解(最新整理)

    文章介紹了Java中的包、抽象類、接口以及實現(xiàn)多個接口的概念,包用于組織類,避免命名沖突,抽象類用于定義不能被實例化的類,可以包含抽象方法,接口則定義了一組抽象方法,可以被類實現(xiàn),實現(xiàn)多個接口可以實現(xiàn)類似多繼承的效果,感興趣的朋友一起看看吧
    2025-02-02
  • SpringBoot?SpringSecurity?JWT實現(xiàn)系統(tǒng)安全策略詳解

    SpringBoot?SpringSecurity?JWT實現(xiàn)系統(tǒng)安全策略詳解

    Spring?Security是Spring的一個核心項目,它是一個功能強(qiáng)大且高度可定制的認(rèn)證和訪問控制框架。它提供了認(rèn)證和授權(quán)功能以及抵御常見的攻擊,它已經(jīng)成為保護(hù)基于spring的應(yīng)用程序的事實標(biāo)準(zhǔn)
    2022-11-11
  • java環(huán)境中的JDK、JVM、JRE詳細(xì)介紹

    java環(huán)境中的JDK、JVM、JRE詳細(xì)介紹

    這篇文章主要介紹了java環(huán)境中的JDK、JVM、JRE詳細(xì)介紹的相關(guān)資料,對于初學(xué)者還是有必要了解下,細(xì)致說明他們是什么,需要的朋友可以參考下
    2016-11-11
  • java時間相關(guān)處理小結(jié)

    java時間相關(guān)處理小結(jié)

    這篇文章介紹了java時間相關(guān)處理,有需要的朋友可以參考一下
    2013-11-11

最新評論

新和县| 和静县| 左权县| 庆阳市| 遵义市| 麦盖提县| 江源县| 新昌县| 五莲县| 岗巴县| 盘锦市| 琼中| 大港区| 买车| 东乌珠穆沁旗| 潞城市| 灵山县| 陈巴尔虎旗| 常山县| 鹤庆县| 阿坝| 吕梁市| 双桥区| 长垣县| 呼图壁县| 蓬莱市| 宁南县| 景洪市| 巴里| 海晏县| 北辰区| 建瓯市| 金山区| 莱芜市| 通榆县| 堆龙德庆县| 沙坪坝区| 西峡县| 西林县| 肥东县| 化德县|