面试官:Spring 事务为什么会失效?

如果你最近也在准备 Java 面试,我整理了一套:

✔ 高频Java面试题(含详细解析)
✔ 模拟真实面试流程训练
✔ 简历优化建议(适合1-5年经验)

已经全部整理成在线版本,方便查阅和练习。

👉 需要的话可以自取:

https://www.myquotego.com


Java面试题

在面试中,Spring 事务失效是一个高频考点,很多候选人回答“事务没加注解”或者“事务传播问题”,却忽略了底层原理导致的真正原因。本文将从技术原理、代码示例到实际应用场景,帮你彻底弄懂Spring事务失效的根本原因。

我整理了一套完整Java面试题库,
完整版在我的技术站:
https://www.myquotego.com/html/questions?_from=csdn_123_1


一、问题背景

在企业级Java应用中,事务管理是保证数据一致性和完整性的核心手段。Spring 提供了基于 AOP 的声明式事务管理,通常通过 @Transactional 注解实现。但在实际开发中,经常会遇到事务没有生效的情况,例如:

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void createUser(User user) {
        userRepository.save(user);
        // 模拟异常
        if (true) {
            throw new RuntimeException("测试事务回滚");
        }
    }
}

预期:userRepository.save(user) 会回滚
实际:数据仍然插入数据库

为什么会出现这种情况?很多人只停留在“事务没加注解”或“异常没捕获”的表面原因,而忽略了 Spring 事务的底层机制。


二、技术原理解析

1. Spring 事务的底层实现

Spring 事务是通过 AOP(代理模式) 实现的。Spring 通过生成代理对象,在方法执行前开启事务,方法执行后提交或回滚事务。

  • JDK 动态代理:当目标类实现接口时,Spring 默认使用 JDK 动态代理,只代理接口方法。
  • CGLIB 代理:当目标类没有实现接口时,Spring 使用 CGLIB 生成子类代理。

核心流程:

  1. 创建代理对象
  2. 调用被代理方法时,事务拦截器 (TransactionInterceptor) 先开启事务
  3. 方法执行过程中出现异常,则回滚事务
  4. 方法正常返回,则提交事务

2. 事务失效的常见原因

原因一:同类内部调用

Spring 事务是基于代理的,如果 同类内部方法调用,事务不会生效,因为调用没有经过代理对象。

@Service
public class UserService {

    @Transactional
    public void createUser(User user) {
        userRepository.save(user);
        updateUser(user); // 内部调用,事务不会生效
    }

    @Transactional
    public void updateUser(User user) {
        user.setName("Updated");
        userRepository.save(user);
    }
}

分析:updateUser 的事务注解不会生效,因为 createUser 内部直接调用,没有经过 Spring 代理。

原因二:异常类型

Spring 默认只对 RuntimeException(及其子类) 回滚,对 Checked Exception 不回滚。

@Transactional
public void saveData() throws IOException {
    // do something
    throw new IOException("Checked异常不会回滚");
}

解决方法:

@Transactional(rollbackFor = Exception.class)
原因三:事务注解位置不当
  • @Transactional 只能放在 public 方法
  • private、protected、default 方法不被代理拦截

3. 事务传播行为与失效场景

Spring 提供了多种事务传播行为,理解传播行为有助于避免事务失效:

传播类型描述典型问题
REQUIRED如果有事务,加入;没有就新建常用
REQUIRES_NEW总是新建事务内部调用可能覆盖外部事务
NESTED嵌套事务需要数据库支持 savepoint

举例:内部调用 REQUIRES_NEW 时,外部事务可能被暂停,导致对数据的回滚感知不同。


三、代码示例

下面给出一个完整示例,演示事务失效与修复:

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private UserService selfProxy; // 使用Spring注入自身代理

    // 正确生效事务
    @Transactional
    public void createUser(User user) {
        userRepository.save(user);
        try {
            selfProxy.updateUser(user); // 通过代理调用,事务生效
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (true) {
            throw new RuntimeException("测试回滚");
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateUser(User user) {
        user.setName("Updated");
        userRepository.save(user);
    }
}

核心技巧:

  1. 自引用注入自身代理,避免内部调用事务失效
  2. rollbackFor 明确指定回滚异常
  3. 保证方法为 public

四、实际应用场景

  1. 电商订单系统:订单创建涉及库存扣减、支付、物流更新,多操作需要保证事务一致性。
  2. 金融交易系统:资金转账要求严格的事务控制,任何异常必须回滚。
  3. 批量数据处理:内部方法调用可能导致事务失效,需要使用代理调用或事务传播策略。

我整理了一套完整Java面试题库,
完整版在我的技术站:
https://www.myquotego.com/html/questions?_from=csdn_123_1


五、总结

Spring 事务失效的根本原因主要在于 代理机制与调用路径,常见失效原因包括:

  1. 内部方法调用
  2. 非 RuntimeException 异常
  3. 方法访问修饰符不正确
  4. 事务传播不理解或使用不当

掌握这些原理,面试官的问题就能迎刃而解,开发中也能避免高并发业务下的数据不一致问题。

我整理了一套完整Java面试题库,
完整版在我的技术站:
https://www.myquotego.com/html/questions?_from=csdn_123_1

关注我,持续更新Java面试核心知识。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值