(可能是最全的)java事务失效场景总结

295 阅读4分钟

写在前面

Java事务失效场景是开发者在使用Spring框架进行事务管理时经常遇到的问题。了解这些场景有助于避免数据不一致和潜在的业务逻辑错误。以下是几种常见的导致Spring事务失效的场景及其原因分析。

事务失效场景

1.没有被Spring管理

若事务方法所在的类未被Spring容器管理(即未通过@Component、@Service等注解注册为Bean),则该类中的事务配置将不起作用。

2.数据库引擎不支持事务

如果数据库引擎本身不支持事务(例如MySQL的MyISAM引擎),即使代码层面正确配置了事务,实际运行时事务也不会生效。确保使用的数据库引擎支持事务操作是非常重要的。

3.非public修饰的方法

Spring AOP代理机制要求被@Transactional注解标记的方法必须是public的。如果将事务方法定义为private、protected访问权限,那么该方法上的事务配置将不会生效。

4.传播行为设置问题

Spring提供了多种事务传播行为选项,如REQUIRED、REQUIRES_NEW等。如果选择了不适合当前业务需求的传播行为,可能会导致事务失效。例如,使用Propagation.NOT_SUPPORTED表示不以事务方式执行,这意味着即使存在事务也会暂停。

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void updateInventory(Item item) {
    inventoryRepository.update(item); // 不支持事务,即使存在也不会创建新事务
}

5.没有配置事务管理器

如果没有配置以下 DataSourceTransactionManager 数据源事务管理器,那么事务也不会生效。在 Sprimg Boot中只要引入了spring-boot-starter-data-jdbc启动器依赖就会自动配置DataSourceTransactionManager 数据源事务管理器,所以Spring Boot框架不存在这个问题,但在传统的 Spring 框架中需要注意。

6.自调用问题

当一个类的方法直接调用本类中另一个方法时,即使第二个方法上标注了@Transactional注解,由于缺乏外部调用,Spring AOP代理无法介入,因此事务不会生效。

// 错误实例
@Service
public class OrderService {
    @Transactional
    public void placeOrder(Order order) {
        saveOrder(order); // 直接调用同一个类中的方法,事务不会生效
    }
​
    @Transactional
    public void saveOrder(Order order) {
        orderRepository.save(order);
    }
}
​
//正确示例
@Service
public class OrderService {
    @Autowired
    private OrderService self;
​
    @Transactional
    public void placeOrder(Order order) {
        self.saveOrder(order); // 使用代理对象进行调用,保证事务生效
    }
​
    @Transactional
    public void saveOrder(Order order) {
        orderRepository.save(order);
    }
}

7.异常处理不当

Spring事务默认只会在遇到运行时异常(RuntimeException及其子类)或Error时才会回滚事务。如果你捕获了异常但没有重新抛出,或者你抛出了检查异常(Checked Exception),而没有指定rollbackFor属性,则事务不会回滚。

// 错误示例
@Transactional
public void processPayment(Payment payment) {
    try {
        paymentGateway.charge(payment);
    } catch (Exception e) {
        log.error("Payment processing failed", e);
        // 捕获异常但没有重新抛出,事务不会回滚
    }
}
​
// 正确示例
@Transactional(rollbackFor = Exception.class)
public void processPayment(Payment payment) {
    try {
        paymentGateway.charge(payment);
    } catch (Exception e) {
        log.error("Payment processing failed", e);
        throw new RuntimeException(e); // 重新抛出异常,确保事务回滚
    }
}

8.使用final或static关键字

如果某个方法被声明为final或static,它就不能被Spring AOP代理重写,从而导致事务失效。这是因为Spring依赖于动态代理来实现事务功能。

9.多线程环境下的事务管理

在多线程环境中,每个线程都有自己的事务上下文,如果不适当处理,可能导致事务失效。通常需要特别注意如何在并发环境下正确地管理事务。

// 错误示例
@Transactional
public void createUserAndAssignRole(User user, Role role) throws ExecutionException, InterruptedException {
    // 在主线程中插入用户信息
    userRepository.save(user);
    
    // 异步地在子线程中插入角色信息
    CompletableFuture.runAsync(() -> {
        roleRepository.save(role);
    }, customThreadPool).join(); // 使用自定义线程池并等待子线程完成
}

在这个例子中,如果roleRepository.save(role)抛出了异常,由于runAsync方法是在一个独立的线程中执行的,Spring的声明式事务管理不会自动回滚主线程中的用户信息插入操作。这是因为每个线程都有自己的ThreadLocal上下文,事务信息不会自动传播到子线程。

参考

mp.weixin.qq.com/s/yeso8F9rL…