避坑 | @Transactional注解下,事务失效的10种场景

在Java应用程序中,Spring框架的事务管理是确保数据完整性和一致性的核心机制之一。然而,在使用Spring的@Transactional注解时,开发人员经常会面临一些可能导致事务失效的场景,从而影响系统的可靠性和稳定性。
本文将深入探讨十种常见场景,并提供详细的解释和代码示例,以帮助大家更好地理解并避免这些问题的发生。

  1. 异常被捕获并处理

在@Transactional注解标记的方法内部抛出异常,但在try-catch块中被捕获并处理时,事务可能会失效。由于异常被捕获,事务管理器无法感知异常的抛出,从而无法触发事务回滚。

@Transactional
public void updateData() {
    try {
        // 可能会抛出异常的操作
    } catch (Exception ex) {
        // 异常被捕获并处理
    }
}
  1. 抛出的异常为受检查异常

如果在@Transactional注解标记的方法内部抛出了受检查异常,而不是RuntimeException或其子类,事务可能会失效。需要额外的配置才能使受检查异常触发事务回滚。

//@Transactional(rollbackFor = Exception.class)//表示该方法的事务在抛出任何异常时都进行回滚
@Transactional
public void loadData() throws SQLException {
    // 可能会抛出受检查异常的操作
}
  1. 方法内部直接调用另一个方法

如果在@Transactional注解标记的方法内部直接调用另一个方法,并且被调用的方法中存在事务操作,但调用是直接的而不是通过代理对象,事务可能会失效。

@Transactional
public void performTransaction() {
    // 可能调用其他方法的操作
    anotherMethod(); // 直接调用另一个方法
}

@Transactional
public void anotherMethod() {
    // 另一个方法可能包含事务操作
}

因为,当你使用@Transactional注解标记一个方法时,Spring会为这个方法创建一个事务代理。这个代理会在方法执行前后负责管理事务。但是,如果你在被@Transactional注解标记的方法内部直接调用另一个方法,而不是通过代理对象调用,那么事务可能会失效,因为Spring无法感知到这个直接的方法调用。

为了解决这个问题,可以这样做:

(1)通过代理对象调用方法:可以注入当前类的代理对象,并使用代理对象来调用另一个方法。这样Spring就能正确地管理被调用方法中的事务。

@Resource
private YourClassName proxy;

@Transactional
public void performTransaction() {
    // 可能调用其他方法的操作
    proxy.anotherMethod(); // 通过代理对象调用另一个方法
}

@Transactional
public void anotherMethod() {
    // 另一个方法可能包含事务操作
}

(2)将方法移到另一个类中:你可以将被调用的方法移到另一个类中,并从performTransaction()方法中通过代理对象调用它。

@Transactional
public void performTransaction() {
    // 可能调用其他方法的操作
    anotherService.anotherMethod(); // 通过代理对象调用另一个方法
}

public class AnotherService {
    @Transactional
    public void anotherMethod() {
        // 另一个方法可能包含事务操作
    }
}
  1. 方法为非public

如果@Transactional注解应用在非public方法上,则事务可能会失效。因为Spring AOP默认情况下只会代理public方法。

@Transactional
private void internalOperation() {
    // 非public方法
}
  1. 错误的传播特性设置

当@Transactional注解的传播行为设置错误时,事务可能会失效。以下是几种常见的传播特性:

  • REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • REQUIRES_NEW:创建一个新的事务,并且暂停当前正在执行的事务(如果存在)。
  • SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式执行。
  • NOT_SUPPORTED:以非事务的方式执行操作,如果当前存在事务,则将其挂起。
  • MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • NEVER:以非事务的方式执行操作,如果当前存在事务,则抛出异常。
  • NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建一个新的事务。
@Service
public class OrderServiceImpl {

    @Transactional(propagation = Propagation.NEVER)//以非事务的方式执行操作
    public void cancelOrder(UserModel userModel) {
        // 取消订单
        cancelOrder(orderDTO);
        // 还原库存
        restoreProductStock(orderDTO.getProductId(), orderDTO.getProductCount());
    }
}
  1. 多线程环境下调用事务方法

如果在多线程环境下调用事务方法,并且这些方法不是在同一个线程中执行,事务可能会失效。由于事务管理是基于线程的,所以在updateData()方法内部启动的新线程将无法访问processInThread()方法的事务。因此,updateData()方法中的数据库操作将不会被纳入到任何事务中,导致事务失效。

@Transactional
public void processInThread() {
    // 在多线程中调用事务方法的操作
    new Thread(() -> {
        // 在新线程中执行事务方法
        updateData();
    }).start();
}
  1. 数据库引擎不支持事务

如果使用的数据库引擎不支持事务,如MyISAM,事务将会失效。因为这些数据库引擎不支持事务操作。

CREATE TABLE my_table (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255)
) ENGINE = MyISAM; -- MyISAM 不支持事务
  1. 事务方法未被Spring管理

如果事务方法所在的类没有注册到Spring IOC容器中,即没有被Spring管理,则Spring事务会失效。

//@Service//未被IOC容器管理
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements IProductService {

    @Autowired
    private ProductMapper productMapper;

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateProductStockById(Integer stockCount, Long productId) {
        productMapper.updateProductStockById(stockCount, productId);
    }
}
  1. 方法使用final类型修饰

如果将事务方法定义成final,Spring事务会失效。因为在代理类中无法重写final修饰的方法,从而无法添加事务功能。

@Service
public class OrderServiceImpl {

    @Transactional
    public final void cancel(OrderDTO orderDTO) {
        // 取消订单
        cancelOrder(orderDTO);
    }
}
  1. 未配置开启事务

如果项目中没有配置Spring的事务管理器,即使使用了Spring的事务管理功能,Spring的事务也不会生效。

@Configuration
@EnableTransactionManagement
public class AppConfig {

    @Bean
    public DataSource dataSource() {
        // 配置数据源
        return new DataSource();
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        // 配置事务管理器
        return new DataSourceTransactionManager(dataSource());
    }
}

以上是Spring事务失效的十种常见场景及相应的解释和代码示例。在实际开发中,了解并避免这些情况的发生,将有助于确保系统数据的完整性和一致性,提高系统的稳定性和可靠性。
通过遵循最佳实践和精心配置,开发人员可以确保Spring事务管理在应用程序中的有效运行,从而确保系统在各种情况下都能保持数据的完整性和一致性。

  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
@Transactional注解Spring框架中用于开启事务注解,但是在某些情况下,@Transactional注解可能会失效,导致事务无法正常工作。以下是一些可能导致@Transactional注解失效场景: 1. 在同一个类中的两个@Transactional方法之间的调用:如果在同一个类中的两个@Transactional方法之间进行调用,那么事务注解将被忽略,因为Spring无法拦截这样的调用。 2. 异常被catch后没有重新抛出:如果在@Transactional方法中捕获了异常并在catch块中处理了它,但是没有重新抛出异常,那么事务将被提交,而不是回滚。 3. 事务方法中使用了try-catch块:如果在@Transactional方法中使用了try-catch块,并且在catch块中处理了异常,那么事务将被提交,而不是回滚。 4. 事务方法中使用了ThreadLocal:如果在@Transactional方法中使用了ThreadLocal,那么事务将被提交,而不是回滚。 5. 事务方法中使用了static方法:如果在@Transactional方法中使用了static方法,那么事务将被提交,而不是回滚。 6. 事务方法中使用了private方法:如果在@Transactional方法中使用了private方法,那么事务将被提交,而不是回滚。 7. 事务方法中使用了同步方法:如果在@Transactional方法中使用了同步方法,那么事务将被提交,而不是回滚。 8. 事务方法中使用了非公共方法:如果在@Transactional方法中使用了非公共方法,那么事务将被提交,而不是回滚。 9. 事务方法中使用了final方法:如果在@Transactional方法中使用了final方法,那么事务将被提交,而不是回滚。 10. 事务方法中使用了接口默认方法:如果在@Transactional方法中使用了接口默认方法,那么事务将被提交,而不是回滚。 11. 事务方法中使用了lambda表达式:如果在@Transactional方法中使用了lambda表达式,那么事务将被提交,而不是回滚。 12. 事务方法中使用了异步方法:如果在@Transactional方法中使用了异步方法,那么事务将被提交,而不是回滚。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值