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

作者:幻好
來源:恒生LIGHT雲社群
問題産生場景
項目業務開發中,我們想保證資料送出的原子性,會使用事務送出的方式,比較常用的是使用的
@Transactional
的方式。但是,在某些情況下,會發現事務未生效的情況,本文就來詳細研究下 spring 中事務失效的原因。
注解@Transactional簡介
@Transactional
是 spring 中聲明式事務管理的注解配置方式,相信這個注解的作用大家都很清楚。
@Transactional
注解可以幫助我們把事務開啟、送出或者復原的操作,通過
aop
的方式進行管理。
通過
@Transactional
注解就能讓 spring 為我們管理事務,免去了重複的事務管理邏輯,減少對業務代碼的侵入,使我們開發人員能夠專注于業務層面開發。
常見 Transactional 失效的場景
注解标注方法修飾符為非 public
Transactional 注解标注方法修飾符為非public時,@Transactional注解将會不起作用。
例如以下代碼,
// 定義一個錯誤的@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();
}
}