AI摘要

Spring声明式事务失效八大场景:1.同类方法内部调用;2.异常被吞或非RuntimeException;3.非public方法;4.数据库引擎不支持;5.自注入调用;6.传播行为设错;7.代理未生效;8.异步线程。其中3、5最隐蔽,排查顺序:代理→异常→权限→数据库→传播。

(一) 理解基石:Spring AOP 代理机制

Spring的声明式事务(@Transactional)是基于AOP实现的。AOP的核心是​代理模式​。Spring在运行时为你需要增强的Bean创建一个代理对象(Proxy)。当你调用@Transactional方法时,实际上是代理对象在替你管理事务(开启、提交、回滚)。

有两种代理:

  • ​JDK动态代理:​​ 针对实现了接口的类。代理对象和真实对象是兄弟关系。
  • ​CGLIB代理:​​ 针对未实现接口的类。代理对象是真实对象的子类。

理解了这个,下面的场景就很好懂了。

场景1:方法内部调用(最常见)

@Service
public class OrderService {

    public void createOrder(Order order) {
        // ... 业务逻辑
        this.updateStatus(order); // 事务失效!
    }

    @Transactional
    public void updateStatus(Order order) {
        orderDao.updateStatus(order);
    }
}

​原因:​createOrder方法里的this是目标对象本身,不是被Spring注入的代理对象。调用this.updateStatus()绕过了代理,事务注解自然无效。

​解决:​​ 最佳实践是使用依赖注入,将事务方法拆分到另一个Bean中。

@Service
public class OrderService {
    @Autowired
    private TransactionalService transactionalService;

    public void createOrder(Order order) {
        // ...
        transactionalService.updateStatus(order); // 通过代理对象调用,事务生效
    }
}

@Service
public class TransactionalService {
    @Transactional
    public void updateStatus(Order order) { ... }
}

场景2:异常被捕获或非RuntimeException

@Transactional默认回滚的异常是RuntimeExceptionError

@Transactional
public void updateOrder() {
    try {
        orderDao.update(...);
        int i = 1 / 0; // 抛出ArithmeticException (RuntimeException)
    } catch (Exception e) {
        // 捕获了异常,事务不会回滚!
        logger.error("error", e);
    }
}

@Transactional
public void updateOrder() throws FileNotFoundException {
    orderDao.update(...);
    throw new FileNotFoundException(); // 检查异常,默认不回滚!
}

解决:

// 方案1:在catch中抛出运行时异常
catch (Exception e) {
    throw new RuntimeException("业务失败", e);
}
// 方案2:修改注解,指定回滚异常类型
@Transactional(rollbackFor = Exception.class)

场景3:方法访问权限非public

因为Spring AOP代理(特别是CGLIB)无法代理非public方法。

@Transactional
private void updateSecretData() { ... } // 事务无效!

场景4:数据库引擎不支持事务

如MySQL的MyISAM引擎就不支持事务,必须使用​InnoDB​。

场景5:自身Bean的调用(进阶版)

即使在同一个Service里,通过@Autowired注入自己来调用,也会因为循环依赖和代理时机问题可能导致事务在某些情况下失效,虽然比内部调用好,但也不推荐。

@Service
public class UserService {
    @Autowired
    private UserService self; // 注入自己

    public void methodA() {
        self.methodB(); // 通过代理调用
    }

    @Transactional
    public void methodB() { ... }
}

场景6:事务传播行为设置错误

例如,如果方法上设置了@Transactional(propagation = Propagation.NOT_SUPPORTED),则表示以非事务方式运行,挂起当前事务。

​总结:​​ 遇到事务不生效,就按这个清单逐一排查:​代理 -> 异常 -> 权限 -> 数据库 -> 传播行为​。

版权声明 ▶ 本网站名称:黄磊的博客
▶ 本文标题:Spring事务失效的八大场景,第3个和第5个最隐蔽!
▶ 本文链接:https://www.huangleicole.com/backend-related/31.html
▶ 转载本站文章需要遵守:商业转载请联系站长,非商业转载请注明出处!!

如果觉得我的文章对你有用,请随意赞赏