Spring--spring事务在什么情况下会失效,以及对应的解决方案

发布于:2025-06-24 ⋅ 阅读:(14) ⋅ 点赞:(0)

前言

一般失效是使用了@Transaction注解的情况下,这篇博客就带你详解一下,哪些情况下注解会失效,在开发过程中要避免这些问题和可以及时发现这些问题,并且知道如何去规避和解决


一、Spring事务的基本原理

在深入了解事务失效的场景之前,我们先简单回顾一下Spring事务的工作原理:

  1. Spring通过AOP(面向切面编程)实现声明式事务
  2. 在方法执行前开启事务,执行后根据执行情况提交或回滚事务
  3. 默认情况下,Spring使用动态代理(JDK动态代理)SpringBoot2.x使用(CGLIB动态代理)来实现事务管理

    关于什么是AOP,什么是动态代理这篇博客可以带你深入理解


 

二、Spring事务失效的常见场景

2.1数据库引擎不支持事务

问题描述: 如果使用MySQL且存储引擎是MyISAM,由于MyISAM不支持事务,所以Spring事务会失效。

解决方案:

-- 查看表的存储引擎
SHOW CREATE TABLE table_name;

-- 修改为支持事务的InnoDB引擎
ALTER TABLE table_name ENGINE=InnoDB;

 

2.2类没有被Spring管理

问题描述: 如果类没有加@Service@Component等注解,没有被Spring容器管理,事务自然不会生效。

错误示例:

// 没有Spring注解,不会被Spring管理
public class UserService {
    @Transactional
    public void saveUser(User user) {
        // 事务不会生效
    }
}

正确示例:

@Service  // 加上Spring注解
public class UserService {
    @Transactional
    public void saveUser(User user) {
        // 事务正常工作
    }
}

2.3方法不是public的

问题描述: @Transactional注解只能作用于public方法上,如果方法是private、protected或默认访问级别,事务将不会生效。

错误示例:

@Service
public class UserService {
    @Transactional
    private void saveUser(User user) {  // private方法,事务失效
        userDao.save(user);
    }
}

正确示例:

@Service
public class UserService {
    @Transactional
    public void saveUser(User user) {  // public方法,事务生效
        userDao.save(user);
    }
}

 

2.4同一个类中方法调用(自调用问题)

问题描述: 当同一个类中的一个方法调用另一个有事务的方法时,事务会失效。这是因为Spring的事务是基于AOP代理实现的,同一个类中的方法调用是通过this调用的,不会经过代理。

错误示例:

@Service
public class UserService {
    
    public void saveUserWithoutTransaction(User user) {
        // 直接调用本类的事务方法,事务不会生效
        this.saveUser(user);
    }
    
    @Transactional
    public void saveUser(User user) {
        userDao.save(user);
    }
}

解决方案:

方案1:通过注入自身来调用

@Service
public class UserService {
    @Autowired
    private UserService userService;
    
    public void saveUserWithoutTransaction(User user) {
        // 通过注入的代理对象调用
        userService.saveUser(user);
    }
    
    @Transactional
    public void saveUser(User user) {
        userDao.save(user);
    }
}

2.5异常被catch没有抛出

问题描述: Spring事务默认只在遇到运行时异常(RuntimeException)和Error时才会回滚,如果异常被catch且没有重新抛出,事务不会回滚。

错误示例:

@Service
public class UserService {
    @Transactional
    public void saveUser(User user) {
        try {
            userDao.save(user);
            // 可能抛出异常的操作
            throw new RuntimeException("保存失败");
        } catch (Exception e) {
            // 异常被捕获,没有重新抛出,事务不会回滚
            log.error("保存用户失败", e);
        }
    }
}

正确示例:

@Service
public class UserService {
    @Transactional
    public void saveUser(User user) {
        try {
            userDao.save(user);
            // 可能抛出异常的操作
        } catch (Exception e) {
            log.error("保存用户失败", e);
            // 重新抛出异常,让事务回滚
            throw new RuntimeException("保存用户失败", e);
        }
    }
}

2.6抛出的异常类型不对

问题描述: 默认情况下,Spring只对RuntimeException和Error进行事务回滚,对于受检异常(checked exception)不会回滚。

错误示例:

@Service
public class UserService {
    @Transactional
    public void saveUser(User user) throws Exception {
        userDao.save(user);
        // 抛出受检异常,事务不会回滚
        throw new Exception("保存失败");
    }
}

解决方案:

方案1:配置rollbackFor

@Service
public class UserService {
    @Transactional(rollbackFor = Exception.class)  // 指定所有异常都回滚
    public void saveUser(User user) throws Exception {
        userDao.save(user);
        throw new Exception("保存失败");  // 现在会回滚
    }
}

2.7多线程调用

问题描述: Spring事务是基于ThreadLocal实现的,不同线程之间的事务是独立的。在多线程环境下,子线程的事务和主线程的事务是分离的。

错误示例:

@Service
public class UserService {
    @Transactional
    public void saveUsersAsync(List<User> users) {
        users.forEach(user -> {
            // 新线程中执行,不在当前事务中
            new Thread(() -> {
                userDao.save(user);  // 这个操作不在事务中
            }).start();
        });
    }
}

解决方案:

@Service
public class UserService {
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    public void saveUsersAsync(List<User> users) {
        users.forEach(user -> {
            new Thread(() -> {
                // 在新线程中手动管理事务
                transactionTemplate.execute(status -> {
                    try {
                        userDao.save(user);
                        return true;
                    } catch (Exception e) {
                        status.setRollbackOnly();
                        return false;
                    }
                });
            }).start();
        });
    }
}

 

三、总结

Spring事务失效的原因主要包括:

  1. 环境问题:数据库不支持事务、Spring配置错误
  2. 代理问题:自调用、非public方法
  3. 异常处理问题:异常被捕获、异常类型不匹配
  4. 事务属性问题:传播行为设置错误
  5. 并发问题:多线程环境下的事务隔离

在使用Spring事务时,我们需要:

  • 理解Spring AOP的原理和限制
  • 正确配置事务管理器
  • 合理处理异常
  • 注意方法的访问级别和调用方式
  • 在多线程环境下特别小心

 


网站公告

今日签到

点亮在社区的每一天
去签到