java面试:Spring事务失效的场景有哪些?

Spring事务失效在java场景当中是十分常见的,在某些情况之下,我们希望被事务锁住能正常回滚的代码往往会由于代码编写的并不规范而导致失效,这就会出现编写的代码达不到我们的预期效果的问题,为了能够避免这种问题的发生,我们就需要知道哪些情况之下会发生事务的失效。

而spring的事务失效一般来说会在八种场景之下发生,接下来我们会对这八种情况进行逐一讲解。

1.抛出错误异常

Spring的事务只会检查Run Time exception和error的异常,而假设在@Transational绑定的事务当中用户选择了抛出了与之同级的异常,就会导致事务失效。

解决方案是通过@Transational的rollbackFor属性来指定除了Run Time exception以外的其他异常类也会触发事务的回滚。

演示代码:

java 复制代码
/**
 * 【问题】默认配置 - 事务失效
 * 抛出 BusinessException(受检异常)时,事务不会回滚!
 */
@Transactional
public void createUserWithDefaultConfig(String username) throws BusinessException {
    userRepository.saveUser(username);  // 已执行
    if (username.contains("invalid")) {
        throw new BusinessException("用户名非法");  // ❌ 事务不回滚!
    }
}

/**
 * 【解决方案】使用 rollbackFor 指定受检异常也触发回滚
 */
@Transactional(rollbackFor = BusinessException.class)
public void createUserWithRollbackFor(String username) throws BusinessException {
    userRepository.saveUser(username);
    if (username.contains("invalid")) {
        throw new BusinessException("用户名非法");  // ✅ 事务会回滚!
    }
}

2.直接调用本类方法

Spring的事务是由AOP也就是基于动态代理来实现的,因此会给自动装配的类的方法进行一个try catch的增强,而假设我们不基于动态代理而是直接调用本类方法,那就没有这样的增强,就会导致本类方法的事务失效。

解决方案是通过@Autowired或者getBean()来进行装配,基于动态代理获取到对象以后,就可以解决。

java 复制代码
@Service
class OrderService {
    
    /**
     * 【问题】直接调用本类方法 - 事务失效!
     */
    public void createOrderDirectCall(String orderId, double amount) {
        // 保存订单主表
        orderRepository.saveOrder(orderId, amount);
        
        // ❌ 直接调用本类方法 - this 指向原生对象,不是代理对象!
        this.saveOrderItemWithTransaction(orderId, "商品A");
    }
    
    /**
     * 被调用的内部方法 - 带有事务注解
     * 只有通过代理调用时,事务才会生效
     */
    @Transactional(rollbackFor = Exception.class)
    public void saveOrderItemWithTransaction(String orderId, String item) throws Exception {
        orderRepository.saveOrderItem(orderId, item);
        if (item.equals("商品A")) {
            throw new Exception("库存不足!");
        }
    }
}

3.多线程事务

假设我们通过多线程来调用另外一个事务方法,假设子线程出现异常,会使得主线程的事务都失效,这是由于spring的每一个线程事务的JDBC Connnection都是保存在对应的ThreadLocal上,它们无法共享同一个数据库连接。

解决方案:通过分布式事务的方式来解决。

4.异常被捕获没有抛出

Spring 事务管理器通过感知异常来决定是否回滚:

  • 方法抛出异常 → 事务管理器捕获 → 触发回滚

  • 异常被 try-catch 捕获且未抛出 → 事务管理器无感知 → 正常提交

    java 复制代码
    @Service
    class TransferService {
        
        /**
         * 【问题】异常被吞没,事务失效!
         * 
         * 结果:转出方钱已扣,转入方未收到,数据不一致!
         */
        @Transactional(rollbackFor = Exception.class)
        public void transferWithSwallowedException(Long fromUserId, Long toUserId, double amount) {
            try {
                // 1. 扣减转出账户
                accountRepository.deductBalance(fromUserId, amount);
                
                // 2. 抛出异常(如余额不足)
                if (amount > 1000) {
                    throw new RuntimeException("单笔转账限额 1000 元!");
                }
                
                // 3. 增加转入账户
                accountRepository.addBalance(toUserId, amount);
                
            } catch (Exception e) {
                // ❌ 致命错误:只记录日志,不抛出异常!
                log.error("转账异常", e);
                
                // 事务管理器感知不到异常,会提交事务!
                // 结果:转出方钱已扣,转入方未收到,数据不一致!
            }
        }
    }

    解决方案:抛出异常,Throw e 即可。

5.@Transational的应用在非public的方法上

非公共方法不应用事务,因此事务会因此失效

6.@Transational应用在final和static修饰的方法上

fianl方法无法被子类化,而static修饰的属于类而不是实例对象,无法被代理

7事务的传播行为配置错误

例如REQUIRES_NEW 滥用导致数据不一致

java 复制代码
@Service
class OrderProcessingService {
    
    @Autowired
    private InventoryService inventoryService;  // 使用 REQUIRES_NEW
    
    @Autowired
    private LogService logService;  // 使用 REQUIRES_NEW
    
    @Transactional(rollbackFor = Exception.class)
    public void createOrder(String orderId, String productId, int quantity) {
        // 1. 保存订单(主事务)
        orderRepository.saveOrder(orderId, quantity * 100.0);
        
        // 2. 扣减库存(独立事务!)
        // ❌ 错误:使用 REQUIRES_NEW,与主事务分离
        inventoryService.deductStock(productId, quantity);
        
        // 3. 记录日志(独立事务!)
        // ❌ 错误:使用 REQUIRES_NEW,与主事务分离
        logService.saveLog("CREATE_ORDER", "订单: " + orderId);
        
        // 4. 模拟异常
        if (quantity > 10) {
            throw new RuntimeException("超出限制!");
        }
        // 结果:主事务回滚(订单未创建),但库存已扣、日志已记录!
    }
}

@Service
class InventoryService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)  // ❌ 错误
    public void deductStock(String productId, int quantity) {
        inventoryRepository.deductStock(productId, quantity);
    }
}

8数据库用的是MyISAM的引擎

MyISAM不支持事务。

今天的分享就到这里了,希望这篇博客能给你一些帮助,让你对关于有了解过Spring事务失效的场景有哪些的问题得到进一步的提升,在面试的时候能从容面对面试官。

相关推荐
二月十六2 小时前
运行 ‘XXXX‘ 时出错 运行 XXXX时出错。命令行过长。 通过 JAR 清单或通过类路径文件缩短命令行,然后重新运行。
java·jar
发现一只大呆瓜2 小时前
React-深度拆解 React路由:从实战进阶到底层原理
前端·react.js·面试
wenlonglanying2 小时前
mysql之联合索引
数据库·mysql
Aloha_up2 小时前
redis与数据库的一致性问题分析
数据库·redis·缓存
牢七2 小时前
jfinal_cms-v5.1.0 审计黑盒
数据库
发现一只大呆瓜2 小时前
React-手把手带你实现 Keep-Alive 效果
前端·react.js·面试
zzh0812 小时前
MySQL数据库操作笔记
数据库·笔记·mysql
6+h2 小时前
【Redis】底层原理解析(SDS / 跳表 / IO多路复用 / 单线程模型)
数据库·redis·bootstrap
idolao3 小时前
CentOS 7 安装 nginx-1.3.15.tar.gz 详细步骤(从源码编译到启动配置)
linux·运维·数据库