Java面试——Spring事务失效的几种场景

@Transactional在private方法上不生效,因Spring事务基于代理机制,仅public方法可被AOP拦截;private方法调用绕过代理,事务逻辑无法织入。

为什么@Transactional在private方法上不生效

Spring事务基于代理机制,只有被代理对象的public方法才能被AOP拦截并织入事务逻辑。private方法无法被代理调用,即使加了@Transactional注解,也完全不会触发事务管理器。

  • 调用链中若从本类内部直接调用private void doUpdate(),事务不会开启
  • 解决办法是把逻辑抽到另一个@Service类中,通过Spring容器注入调用
  • 或改用TransactionTemplate手动控制事务,但会牺牲声明式事务的简洁性

同一类中this调用导致事务失效

当一个@Service类里,methodA()(带@Transactional)调用本类的methodB()(也带@Transactional),实际执行的是this.methodB()——走的是原始对象调用,绕过了代理对象,事务不会传播。

  • 现象:methodB抛出异常,methodA的事务不回滚
  • 根本原因:Spring代理只对“外部Bean调用”生效,this指向当前实例,不是代理对象
  • 验证

    方式:在methodB开头打印this.getClass(),会看到类似UserService$$EnhancerBySpringCGLIB$$xxx以外的原始类名

RuntimeException之外的异常未配置rollbackFor

Spring默认只对RuntimeException及其子类、Error触发回滚。如果业务抛出的是Exception(比如IOException、自定义检查异常),且没显式配置rollbackFor,事务会正常提交。

  • 常见错误写法:
    @Transactional
    public void transfer() throws BusinessException {
    // ...
    throw new BusinessException("余额不足");
    }
  • 正确做法:
    @Transactional(rollbackFor = BusinessException.class)
    public void transfer() throws BusinessException {
    // ...
    }
  • 注意:rollbackFor支持数组,可同时指定多个异常类型;noRollbackFor用于排除特定异常

数据库引擎不支持事务或事务已提交

事务失效有时和Spring无关,而是底层环境问题。最典型的是MySQL表使用MyISAM引擎——它根本不支持事务,无论注解怎么写都无效。

  • 检查方式:
    SHOW CREATE TABLE user;
    确认引擎是否为InnoDB
  • 另一个隐蔽情况:事务方法内调用了JdbcTemplate.update()后又手动调用connection.commit(),会导致Spring事务管理器失去控制权
  • 此外,若事务方法被标记为propagation = Propagation.NOT_SUPPORTED,当前操作会挂起事务,也不受事务保护
事务失效往往不是单一原因,而是代理机制、异常策略、数据库配置、传播行为四者交织的结果。最容易被忽略的是“自己调自己”和“引擎不支持”这两点——前者在本地测试时可能恰好不暴露问题,后者在线上环境才突然爆发。