先分析一下
1,内部调用,@原对象调用,不是代理对象调用
2,private方法,@源码中,只能是public方法
3,异常被捕获了,@事物拦截器,无法感知
4,子线程调用,@事物TreadLocal实现,主子线程独立
5,Roback 设置不对,@默认对RuntimeExcpetion+error有效,sql,io无效,@Exception
6,传播机制不对,@被调用方法B是new,A调用B,A异常,B不会滚,他是新的事物
7,存储引擎不是innodb
8,事物未开启
DS
@Transactional
注解失效的场景及原因分析
@Transactional
是 Spring 声明式事务的核心注解,但在某些场景下会失效。以下是常见的失效场景及其根本原因:
1. 自调用(同一个类中方法调用)
场景:
java
复制
下载
public class OrderService {public void createOrder() {this.updateStatus(); // 自调用,事务失效}@Transactionalpublic void updateStatus() {// 业务逻辑} }
原因:
-
Spring 事务基于 AOP 代理实现,自调用时通过
this
调用的原始对象而非代理对象,导致拦截器无法介入。
解决方案:
-
注入自身代理:
@Autowired private OrderService self;
然后调用self.updateStatus()
-
将方法拆分到不同类中
2. 方法访问权限非 public
场景:
java
复制
下载
@Transactional private void updateData() { // 事务失效// ... }
原因:
-
Spring 默认只代理 public 方法(CGLIB 可代理 protected,但官方不建议)。
-
动态代理(JDK/CGLIB)无法拦截 private 方法。
解决方案:
-
将方法改为
public
-
改用编程式事务(
TransactionTemplate
)
3. 异常类型不匹配
场景:
java
复制
下载
@Transactional public void process() throws Exception {try {// 可能抛出IOException} catch (IOException e) {throw new Exception("业务异常"); // 非RuntimeException} }
原因:
-
默认只对
RuntimeException
和Error
回滚,受检异常(如Exception
)不会触发回滚。
解决方案:
-
明确指定回滚异常:
java
复制
下载
@Transactional(rollbackFor = Exception.class)
-
抛出
RuntimeException
或其子类
4. 数据库引擎不支持事务
场景:
-
使用 MyISAM 引擎的 MySQL 表
-
某些 NoSQL 数据库(如 MongoDB 旧版本)
原因:
-
MyISAM 不支持事务,仅 InnoDB 支持。
-
部分数据库无事务能力。
解决方案:
-
切换为 InnoDB 引擎:
sql
复制
下载
ALTER TABLE your_table ENGINE=InnoDB;
5. 多线程调用
场景:
java
复制
下载
@Transactional public void asyncTask() {new Thread(() -> {// 子线程中的操作不受事务控制}).start(); }
原因:
-
事务信息通过
ThreadLocal
存储,子线程无法继承父线程的事务上下文。
解决方案:
-
使用
TransactionTemplate
编程式事务 -
通过异步事件(如
@TransactionalEventListener
)处理
6. 未启用事务管理
场景:
-
忘记添加
@EnableTransactionManagement
-
配置错误(如错误的
PlatformTransactionManager
Bean)
原因:
-
Spring 未扫描到
@Transactional
注解或未创建代理。
解决方案:
-
确保配置类添加注解:
java
复制
下载
@Configuration @EnableTransactionManagement // 关键 public class AppConfig { ... }
-
检查事务管理器 Bean 是否正确注入
7. 嵌套事务传播行为不当
场景:
java
复制
下载
@Transactional(propagation = Propagation.REQUIRED) public void methodA() {methodB(); }@Transactional(propagation = Propagation.REQUIRES_NEW) public void methodB() {// 如果methodA抛异常,methodB仍会提交 }
原因:
-
REQUIRES_NEW
会挂起当前事务,独立提交。若外层事务回滚,内层事务不受影响。
解决方案:
-
根据业务需求调整传播行为(如改用
NESTED
)
8. 事务方法内捕获异常未重新抛出
场景:
java
复制
下载
@Transactional public void process() {try {// 可能抛出异常的操作} catch (Exception e) {log.error("错误", e); // 仅记录不抛出} }
原因:
-
拦截器无法感知异常,事务标记为成功。
解决方案:
-
捕获后手动回滚:
java
复制
下载
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
-
或重新抛出异常
总结:事务失效排查清单
失效场景 | 关键特征 | 验证方法 |
---|---|---|
自调用 | 同类中非代理调用 | 检查是否通过 this 调用 |
非 public 方法 | 方法为 private/protected | 查看方法修饰符 |
异常类型不匹配 | 捕获后未抛出或抛出受检异常 | 检查异常处理逻辑 |
数据库引擎不支持 | 使用 MyISAM 引擎 | SHOW TABLE STATUS 查看引擎 |
多线程调用 | 子线程内操作 | 检查是否有 new Thread() |
未启用事务管理 | 无事务管理器 Bean | 检查 @EnableTransactionManagement |
嵌套事务传播不当 | 内外层事务相互影响 | 检查 propagation 配置 |
捕获异常未处理 | try-catch 吞没异常 | 检查日志是否有未抛出的异常记录 |
终极调试技巧:
在调试模式下查看目标对象类名:
-
代理对象:
com.sun.proxy.$ProxyXX
(JDK)或UserService$$EnhancerBySpringCGLIB
-
原始对象:
com.example.UserService
若调用的是原始对象,则事务必然失效。