必看!導(dǎo)致事務(wù)失效的七大典型場景!
@Transactional 聲明式事務(wù)失效的場景是 Java 面試中經(jīng)常被問到的問題,所以今天咱們就來系統(tǒng)的盤點(diǎn)一下導(dǎo)致 @Transactional 失效的場景有哪些?以及導(dǎo)致的原因和解決方案詳解。
1.方法訪問修飾符不是public
以下代碼會導(dǎo)致 @Transactional 失效:
@Service
public class OrderService {
@Transactional
private void createOrder() { // private方法,事務(wù)不生效
// ...
}
}原因分析
Spring AOP 代理在生成代理類時,只對 public 方法生成事務(wù)代理,這是 Java 源碼層面設(shè)計(jì)原因,設(shè)計(jì)源碼如下:
protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {
// Don't allow no-public methods as required.
// 非 public 方法,設(shè)置為 null
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// 后面代碼省略....
}解決方案
將方法改為 public。
2.自調(diào)用問題
以下代碼會導(dǎo)致 @Transactional 失效:
@Service
public class OrderService {
public void placeOrder() {
createOrder(); // 直接調(diào)用,繞過代理
}
@Transactional
public void createOrder() {
// 事務(wù)操作
}
}原因分析
當(dāng)一個類中的非事務(wù)方法調(diào)用本類的事務(wù)方法時,調(diào)用是通過 this 直接調(diào)用,而不是通過 Spring 代理對象調(diào)用,因此事務(wù)不能生效。
解決方案
- 將事務(wù)方法移到另一個 Service 類中
- 使用 ApplicationContext 獲取當(dāng)前 Bean 的代理對象,如下代碼所示:
@Autowired
private ApplicationContext context;
public void placeOrder() {
OrderService proxy = context.getBean(OrderService.class);
proxy.createOrder(); // 通過代理調(diào)用
}3.異常被捕獲且未重新拋出
以下代碼會導(dǎo)致 @Transactional 失效:
@Transactional
public void transferMoney() {
deductMoney();
try {
addMoney();
} catch (Exception e) {
log.error("異常", e);
// 捕獲但未拋出,事務(wù)不會回滾
}
}原因分析
Spring 事務(wù)默認(rèn)只在拋出未被捕獲的 RuntimeException 或 Error 時回滾。如果異常被捕獲且未拋出,代理認(rèn)為方法執(zhí)行成功,會提交事務(wù)。
解決方案
- 手動設(shè)置回滾:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
- 或重新拋出異常:
@Transactional
public void transferMoney() {
deductMoney();
try {
addMoney();
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
throw e; // 或不拋出,僅setRollbackOnly
}
}4.事務(wù)傳播機(jī)制配置錯誤
例如:使用了 Propagation.NOT_SUPPORTED 或 Propagation.NEVER,會導(dǎo)致事務(wù)被掛起或拒絕。
特別注意 Propagation.REQUIRES_NEW:會掛起當(dāng)前事務(wù),開啟新事務(wù),需謹(jǐn)慎使用,避免嵌套事務(wù)導(dǎo)致性能問題或死鎖。
5.數(shù)據(jù)庫引擎不支持事務(wù)
例如:MySQL 的 MyISAM 引擎不支持事務(wù),必須使用 InnoDB。
即使代碼配置了@Transactional,底層數(shù)據(jù)庫不支持也無法實(shí)現(xiàn)事務(wù)。
6.方法被final或static修飾
CGLIB 無法代理 final 方法(不能被重寫),JDK 代理也無法處理 static 方法,都會導(dǎo)致事務(wù)無法生效。因?yàn)?Spring/Spring Boot 是使用 CGLIB 或 JDK 代理實(shí)現(xiàn)的。
7.多線程中調(diào)用事務(wù)方法
以下代碼會導(dǎo)致 @Transactional 失效:
@Transactional
public void process() {
new Thread(() -> {
dao.update(); // 在子線程中,無事務(wù)
}).start();
}原因分析
事務(wù)是基于線程綁定的(通過 ThreadLocal 存儲事務(wù)上下文),子線程中調(diào)用事務(wù)方法時,無法繼承父線程的事務(wù)上下文。
解決方案
使用事務(wù)同步或手動管理事務(wù)。
小結(jié)
@Transactional 聲明式事務(wù)底層是通過 CGLIB 或 JDK 代理實(shí)現(xiàn)的,所以事務(wù)失效的場景多半與二者相關(guān),本文總共介紹了 7 種導(dǎo)致事務(wù)失效的場景,您至少要記住其中 4 種以上事務(wù)失效場景,這樣才能在面試中嶄露頭角。

























