天天看點

項目中Spring事務失效的場景問題排查

​​2022全球C++及系統軟體技術大會 | 3月11-12日·上海 點選了解詳情 》>>>

項目中Spring事務失效的場景問題排查

​​

作者:幻好

來源:​​恒生LIGHT雲社群​​

問題産生場景

項目中Spring事務失效的場景問題排查

項目業務開發中,我們想保證資料送出的原子性,會使用事務送出的方式,比較常用的是使用的 ​

​@Transactional​

​ 的方式。但是,在某些情況下,會發現事務未生效的情況,本文就來詳細研究下 spring 中事務失效的原因。

​注解@Transactional簡介​

​@Transactional​

​​ 是 spring 中聲明式事務管理的注解配置方式,相信這個注解的作用大家都很清楚。​

​@Transactional​

​​ 注解可以幫助我們把事務開啟、送出或者復原的操作,通過 ​

​aop​

​ 的方式進行管理。

通過 ​

​@Transactional​

​ 注解就能讓 spring 為我們管理事務,免去了重複的事務管理邏輯,減少對業務代碼的侵入,使我們開發人員能夠專注于業務層面開發。

項目中Spring事務失效的場景問題排查

常見 Transactional 失效的場景

注解标注方法修飾符為非 public

Transactional 注解标注方法修飾符為非public時,@Transactional注解将會不起作用。

項目中Spring事務失效的場景問題排查

例如以下代碼,

// 定義一個錯誤的@Transactional标注實作,修飾一個預設通路符的方法
@Component
public class TestServiceImpl {
    @Resource
    TestMapper testMapper;

    @Transactional
    void insertTestWrongModifier() {
        int re = testMapper.insert(new Test(10,20,30));
        if (re > 0) {
            throw new NeedToInterceptException("need intercept");
        }
        testMapper.insert(new Test(210,20,30));
    }
}

// 同一個包,建立調用對象,進行通路
@Component
public class InvokcationService {
    @Resource
    private TestServiceImpl testService;
    public void invokeInsertTestWrongModifier(){
        // 調用@Transactional标注的預設通路符方法
        testService.insertTestWrongModifier();
    }
}

// 測試
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
   @Resource
   InvokcationService invokcationService;

   @Test
   public void  testInvoke(){
      invokcationService.invokeInsertTestWrongModifier();
   }
}      

以上的通路方式,導緻事務沒開啟,是以在方法抛出異常時,資料庫的插入操作将不會復原,如果将方法改為 ​public​ 則事務将生效并正常復原。

​注意:​

​protected​

​​、​

​private​

​ 修飾的方法上使用 ​

​@Transactional​

​ 注解,雖然事務無效,但不會有任何報錯,這是我們很容犯錯的一點。​

同一類内部調用标注的方法

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

示例代碼如下。

// 設定一個内部調用
@Component
public class TestServiceImpl implements TestService {
    @Resource
    TestMapper testMapper;

    @Transactional
    public void insertTestInnerInvoke() {
        // 正常public修飾符的事務方法
        int re = testMapper.insert(new Test(10,20,30));
        if (re > 0) {
            throw new NeedToInterceptException("need intercept");
        }
        testMapper.insert(new Test(210,20,30));
    }


    public void testInnerInvoke(){
        // 類内部調用@Transactional标注的方法。
        insertTestInnerInvoke();
    }
}

// 測試
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {

   @Resource
   TestServiceImpl testService;

   /**
    * 測試内部調用@Transactional标注方法
    */
   @Test
   public void  testInnerInvoke(){
       //測試外部調用事務方法是否正常
      //testService.insertTestInnerInvoke();
       //測試内部調用事務方法是否正常
      testService.testInnerInvoke();
   }
}      

上面就是使用的測試代碼,調用一個方法在類内部調用内部被@Transactional标注的事務方法,運作結果是事務不會正常開啟,插入操作儲存到資料庫也不會進行復原。

事務方法内部捕捉異常

事務方法内部捕捉了異常,沒有抛出新的異常,導緻事務操作不會進行復原。

示例代碼如下。

@Component
public class TestServiceImpl implements TestService {
    @Resource
    TestMapper testMapper;

    @Transactional
    public void insertTestCatchException() {
        try {
            int re = testMapper.insert(new Test(10,20,30));
            if (re > 0) {
                //運作期間抛異常
                throw new NeedToInterceptException("need intercept");
            }
            testMapper.insert(new Test(210,20,30));
        }catch (Exception e){
            System.out.println("i catch exception");
        }
    }
}

// 測試
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {

   @Resource
   TestServiceImpl testService;

   @Test
   public void testCatchException(){
      testService.insertTestCatchException();
   }
}      

總結