<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          百度一面:談?wù)?@Transactional 的原理和坑

          共 8459字,需瀏覽 17分鐘

           ·

          2022-10-15 15:30

          大家好,我是樓仔!

          Java 后端面試的時候,面試官經(jīng)常會問到 @Transactional 的原理,以及容易踩的坑,之前一面百度,就遇到過,今天就帶大家把這幾塊知識吃透。

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

          項目準(zhǔn)備

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

          uidunameusex
          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ù)生效");
              }
          }

          事務(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)用,線程拋出異常。

          Case 1: 類內(nèi)部訪問

          我們在類 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.testSuccess();
              } finally {
                  MyUser user =  uc.query(1);
                  System.out.println("修改后的記錄:" + user);
              }
          }
          // 輸出:
          // 原記錄:MyUser(uid=1, uname=張三, usex=女)
          // 修改后的記錄:MyUser(uid=1, uname=張三-testing, usex=女)

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

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

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

          如果還是不太清楚,推薦再看看這篇文章,里面有完整示例,非常完美詮釋“類內(nèi)部訪問”不能前后增強的原因:https://blog.csdn.net/Ahuuua/article/details/123877835

          Case 2: 私有方法

          在私有方法上,添加 @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,至于深層次的原理,源碼部分會給你解讀。

          Case 3: 異常不匹配

          這里的 @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ù)不生效:異常不匹配");
          }
          測試方法:同 Case1

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

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

          Case 4: 多線程

          下面給出兩個不同的姿勢,一個是子線程拋異常,主線程 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ù)回滾沒有生效。

          源碼解讀

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

          @Transactional 執(zhí)行機制

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

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

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

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

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

          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

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

          異常不匹配原因

          我們繼續(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í)行回滾。



          END


          關(guān)注 Stephen,一起學(xué)習(xí),一起成長。

          “在看”支持下吧


          點 閱讀原文 可優(yōu)惠充值話費,流量,視頻會員等。

          瀏覽 57
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲中文免费视频 | 亚洲www啪成人一区二区麻豆 | 中文字幕无码一区二区三区一本久道不卡 | 国产搞黄色片网 | 又粗又大又黄的视频 |