在哪些场景中会出现事务失效

77 阅读3分钟

在 Java 开发中,Spring 声明式事务失效的常见场景主要与 AOP 代理机制、异常处理、配置错误等因素相关。

一.代理机制被绕过

  1. 非public方法

Spring AOP默认只代理public方法,若事务方法为private,protected或包级可见,代理无法生效.

@Transactional // 失效
private void saveData() {
    // ...
}

解决方案:将方法声明为public

  1. 同类方法自调用 当同类方法通过this调用本类事务方法时,实际调用的是目标对象而非代理对象,导致事务拦截失效 java
@Service
public class UserService {
    @Transactional
    public void updateUser() {
        // 业务逻辑
        this.insertLog(); // 自调用,事务失效
    }
    
    @Transactional
    public void insertLog() {
        // 日志插入
    }
}

解决方案:通过依赖注入自身代理对象(需开启@EnableAspectJAutoProxy(exposeProxy = true))但是注入自身又会带来另一个问题或拆分方法到其他类

  1. 方法被final/static修饰

final方法无法被CGLIB代理,static方法无法被动态代理拦截

解决方案:移除final/static修饰符

二. 异常处理不当

  1. 异常被捕获未抛出

若事务方法内部捕获异常但未重新抛出,Spring无法感知到异常并回滚

@Transactional
public void transfer() {
    try {
        // 转账逻辑
        int i = 1/0; // 模拟异常
    } catch (Exception e) {
        e.printStackTrace(); // 未抛出异常,事务不回滚
    }
}

解决方案:在catch块中崇信抛出异常或调用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()

2.异常类型不匹配 Spring默认仅回滚RuntimeExceptionError,受检异常(如IOException)需显示配置.

@Transactional // 抛出IOException时不回滚
public void upload() throws IOException {
    // ...
}
@Transactional(rollbackFor = Exception.class) // 抛出IOException时会回滚
public void upload() throws IOException {
    // ...
}

解决方案:通过rollbackFor属性指定异常类型:
@Transactional(rollbackFor = Exception.class)

三.事务配置错误

  1. 传播行为设置错误

错误的传播行为(如NOT_SUPPORTED)可能导致事务挂起或失效

@Transactional(propagation = Propagation.NOT_SUPPORTED) // 当前事务被挂起
public void subMethod() {
    // ...
}

解决方案:根据业务需求选择合适的传播行为(如REQUIREDREQUIRES_NEW)

2.事务管理器未配置 多数据源场景下未指定事务管理器,或未启用事务管理注解。

@Transactional // 未指定transactionManager,多数据源时失效
public void operate() {
    // ...
}

解决方案:配置@EnableTransactionManagement并在注解中指定transactionManager

  1. 数据库引擎不支持事务 MySQL 的 MyISAM 引擎不支持事务,需使用 InnoDB。
    解决方案:修改表引擎为 InnoDB

四.多线程与上下文丢失

  1. 跨线程调用

事务上下文绑定在当前线程,新线程无法继承父线程事务

@Transactional
public void asyncTask() {
    new Thread(() -> {
        // 数据库操作,事务失效(新线程无事务上下文)
    }).start();
}

解决方案: 避免在事务方法中开启新线程,或使用@Async时结合事务传播属性

五.其他隐蔽场景

1.类未被 Spring 管理
若事务类未通过@Component等注解注册到 IOC 容器,代理无法生效。

public class UserService {
    @Transactional // 失效
    public void save() {
        // ...
    }
}

解决方案:添加@Service等注解,确保类被 Spring 托管 2. 切面顺序问题
若自定义切面优先级高于事务切面,可能导致异常未被事务切面捕获。
解决方案:通过@Order注解调整切面顺序,确保事务切面优先级最低(如@Order(Ordered.LOWEST_PRECEDENCE)

  1. 嵌套事务误用 NESTED传播行为依赖数据库保存点,部分数据库(如 Oracle)支持不完整,可能导致回滚异常。
    解决方案:优先使用REQUIRES_NEW实现独立事务