面试官:工作中,你有遇到过Spring事务失效的时候吗?

276 阅读5分钟

 在日常开发中,Spring事务管理是保证数据一致性的重要手段,但你是否遇到过明明加了@Transactional注解,事务却“神秘失效”的情况?这种问题不仅让新手抓狂,连老司机也偶尔翻车。本文总结了12种常见的Spring事务失效场景,结合代码示例和解决方案,帮你彻底避开这些坑!

一、访问权限问题

失效原因:Spring事务基于AOP代理实现,默认要求被代理方法必须是public。如果方法权限是privatedefaultprotected,事务会直接失效。
示例代码

@Service
public class UserService {
    @Transactional
    private void add(UserModel userModel) {  // 错误:private方法
        saveData(userModel);
    }
}

解决方案:确保事务方法为public


二、final或static方法

失效原因:final或static方法无法被动态代理类重写,导致事务失效。
示例代码

@Service
public class UserService {
    @Transactional
    public final void add(UserModel userModel) {  // 错误:final方法
        saveData(userModel);
    }
}

解决方案:避免在事务方法上使用finalstatic


三、方法内部调用

失效原因:在同一个类中,非事务方法A调用事务方法B时,实际调用的是原对象的方法,而非代理对象,导致事务失效。

假设有一个类 MyService,其中有一个事务方法 transactionalMethod() 和一个非事务方法 nonTransactionalMethod()

@Service
public class MyService {
    @Transactional
    public void transactionalMethod() {
        // 事务逻辑
    }

    public void nonTransactionalMethod() {
        // 非事务逻辑
        this.transactionalMethod(); // 直接调用事务方法
    }
}

在这种情况下,nonTransactionalMethod() 中直接调用 transactionalMethod() 时,事务不会生效。因为 this 指代的是当前对象实例,而不是 Spring 生成的代理对象。

解决方案

1.使用 AOP 的 AopContext

通过AopContext.currentProxy()获取代理对象,然后通过代理对象调用事务方法:

@Service
public class MyService {
    @Autowired
    private ApplicationContext applicationContext;

    @Transactional
    public void transactionalMethod() {
        // 事务逻辑
    }

    public void nonTransactionalMethod() {
        // 非事务逻辑
        MyService proxy = (MyService) AopContext.currentProxy();
        proxy.transactionalMethod(); // 通过代理对象调用事务方法
    }
}

2.注入自身

通过注入自身的方式,让 Spring 的代理对象被注入到类中,然后通过代理对象调用事务方法:

@Service
public class MyService {
    @Autowired
    private MyService myService;

    @Transactional
    public void transactionalMethod() {
        // 事务逻辑
    }

    public void nonTransactionalMethod() {
        // 非事务逻辑
        myService.transactionalMethod(); // 通过注入的代理对象调用事务方法
    }
}


四、未被Spring管理

失效原因:如果类没有@Service@Component等注解,Spring不会创建代理对象,事务自然失效。
示例代码

// @Service  // 错误:未添加注解
public class UserService {
    @Transactional
    public void add(UserModel userModel) { ... }
}

解决方案:确保类被Spring管理。


五、多线程调用

失效原因:不同线程使用不同的数据库连接,事务无法跨线程同步。
示例代码

@Transactional
public void add(UserModel userModel) {
    new Thread(() -> {
        roleService.doOtherThing(); // 新线程中的事务独立
    }).start();
}

解决方案:避免在多线程中调用事务方法,或用异步事务框架(如@Async配合事务)。


六、表不支持事务

失效原因:MySQL的MyISAM引擎不支持事务,而InnoDB支持。
建表语句

CREATE TABLE `user` (...) ENGINE=MyISAM;  // 错误:MyISAM引擎

解决方案:使用InnoDB引擎。


七、错误的传播特性

失效原因:错误设置propagation参数可能导致事务不生效。例如,Propagation.NEVER要求不能存在事务,否则抛异常。
示例代码

@Transactional(propagation = Propagation.NEVER) // 错误:当前存在事务
public void add(UserModel userModel) { ... }

解决方案:根据业务场景选择正确的传播特性(默认REQUIRED)。


八、吞掉异常

失效原因:手动捕获异常但未抛出,Spring认为程序正常执行,不会回滚。
示例代码

@Transactional
public void add(UserModel userModel) {
    try {
        saveData(userModel);
    } catch (Exception e) {
        // 吞掉异常,事务不回滚!
    }
}

解决方案:抛出RuntimeException或在catch中手动回滚。


九、抛错异常类型

失效原因:Spring默认只回滚RuntimeExceptionError,普通Exception不会触发回滚。
示例代码

@Transactional
public void add(UserModel userModel) throws Exception {
    throw new Exception("普通异常"); // 事务不回滚!
}

解决方案:配置@Transactional(rollbackFor = Exception.class)


十、自定义回滚异常

失效原因:若rollbackFor指定了特定异常,但实际抛出其他异常,事务不会回滚。
示例代码

@Transactional(rollbackFor = BusinessException.class)
public void add(UserModel userModel) {
    throw new SQLException(); // 不属于BusinessException,不回滚!
}

解决方案:设置rollbackFor = Throwable.class覆盖所有异常。


十一、嵌套事务回滚问题

失效原因:嵌套事务(Propagation.NESTED)中,若内层异常未被捕获,外层事务会整体回滚。
示例代码

@Transactional
public void add(UserModel userModel) {
    userMapper.insert(userModel);
    roleService.doNestedTask(); // 内层事务抛异常,外层也回滚!
}

// RoleService中
@Transactional(propagation = Propagation.NESTED)
public void doNestedTask() { ... }

解决方案:在内层事务中捕获异常并处理,避免异常传递到外层。


十二、未开启事务

失效原因:传统Spring项目需手动配置事务管理器,若忘记配置,事务不生效。
解决方案

  1. SpringBoot项目自动配置事务,无需额外处理。
  2. 传统Spring项目需在XML中配置事务管理器和AOP。

总结

Spring事务失效的常见原因可归结为代理机制、异常处理、配置错误三类。在实际开发中,建议:

  1. 使用public非final方法。
  2. 统一异常处理逻辑,避免吞异常。
  3. 通过日志或调试工具检查事务是否生效。

掌握这些技巧后,面试官再问“事务为什么失效”,你就能从容应对了!