前言
一般失效是使用了@Transaction注解的情况下,这篇博客就带你详解一下,哪些情况下注解会失效,在开发过程中要避免这些问题和可以及时发现这些问题,并且知道如何去规避和解决
一、Spring事务的基本原理
在深入了解事务失效的场景之前,我们先简单回顾一下Spring事务的工作原理:
- Spring通过AOP(面向切面编程)实现声明式事务
- 在方法执行前开启事务,执行后根据执行情况提交或回滚事务
- 默认情况下,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事务失效的原因主要包括:
- 环境问题:数据库不支持事务、Spring配置错误
- 代理问题:自调用、非public方法
- 异常处理问题:异常被捕获、异常类型不匹配
- 事务属性问题:传播行为设置错误
- 并发问题:多线程环境下的事务隔离
在使用Spring事务时,我们需要:
- 理解Spring AOP的原理和限制
- 正确配置事务管理器
- 合理处理异常
- 注意方法的访问级别和调用方式
- 在多线程环境下特别小心