Spring事务失效和异常回滚总结
一 Spring事务传播机制
七大传播机制:
二 Spring事务不生效场景
1 非公共方法上使用事务注解
Java的访问权限主要有四种:private、default、protected、public,它们的权限从左到右,依次变大。
Spring事务是通过AOP(面向切面编程)实现的,只有公共方法上的事务注解才会生效。如果在非公共方法上使用事务注解,事务将不会被管理,导致失效。
// This will not work, as privateMethod() is not public
@Transactional
private void privateMethod() {
// ...
}
2 Final方法上使用事务注解
Spring事务是通过AOP(面向切面编程)实现的,在它的代理类中,无法重写final方法,而添加事务功能,导致失效。
@Transactional
public final void finalMethod() {
// ...
}
3 自调用问题
当在同一个类中的一个@Transactional
方法调用另一个@Transactional
方法时,事务可能会失效。这是因为Spring使用代理机制来实现事务,自调用方法绕过了代理,导致事务管理器无法介入。
@Transactional
public void methodA() {
// ...
methodB(); // This call may not be transactional
}
@Transactional
public void methodB() {
// ...
}
三个解决思路: 1)methodB放到另外一个类中,然后注入进当前类
2)该Service类中注入自己(不推荐)
3)通过AopContext.currentProxy()获取代理对象
4 对象未被Spring管理
即使用spring事务的前提是:对象要被spring管理,需要创建bean实例。
//@Service
public class UserService {
@Transactional
public void add(UserModel userModel) {
saveData(userModel);
updateData(userModel);
}
}
5 多线程调用
如果在一个带有@Transactional
注解的方法中启动一个新线程,新线程的操作将在一个新的独立事务中运行,而不是与原始事务关联。如果在新线程中发生异常,它将独立于原始事务的异常处理,原始事务可能不会回滚。 以下是一个简单的示例,说明在多线程情况下Spring事务可能失效:
@Service
public class MyService {
@Autowired
private MyRepository repository;
@Transactional
public void performTransactionalOperation() {
// 一些业务逻辑
// 在事务中启动新线程
new Thread(() -> {
// 新线程中的操作,这不会与原始事务关联
repository.updateDataInNewThread();
// 可能发生异常,但原始事务不受影响
throw new RuntimeException("Exception in new thread");
}).start();
// 主线程中的一些其他操作
// ...
}
}
@Repository
public class MyRepository {
@PersistenceContext
private EntityManager entityManager;
@Transactional
public void updateDataInNewThread() {
// 更新数据库操作
// ...
}
}
6 表不支持事务
如MySQL的数据库引擎myisam
7 没有启用事务管理器
检查应用的配置文件,确保@EnableTransactionManagement
注解被正确添加到配置类中。如果没有启用事务管理,事务将不会生效。
@Configuration
@EnableTransactionManagement
public class AppConfig {
// 配置类
}
如果你是SpringBoot项目,那么 TransactionAutoConfiguration
是Spring Boot中的一个自动配置类,用于配置和启用Spring事务管理。这个自动配置类通常会自动生效,无需手动配置,它通过类路径下的相关依赖来判断是否需要启用事务管理。
以下是TransactionAutoConfiguration
的一些关键点:
条件判断:
TransactionAutoConfiguration
类使用@ConditionalOnClass
注解,该注解表示只有在类路径下存在指定的类时,自动配置才会生效。具体来说,它通常会检查是否存在PlatformTransactionManager
类。@Configuration(proxyBeanMethods = false) @ConditionalOnClass(PlatformTransactionManager.class) @EnableTransactionManagement @ConditionalOnMissingBean(TransactionManagementConfigurationSelector.class) public class TransactionAutoConfiguration { // ... }
@EnableTransactionManagement
: 这个注解用于启用Spring的注解驱动的事务管理。它会自动注册TransactionManagementConfigurationSelector
,该选择器在符合条件的情况下会注册ProxyTransactionManagementConfiguration
。条件注解:
TransactionAutoConfiguration
类还使用了@ConditionalOnMissingBean
注解,这表示只有在容器中不存在某个特定类型的bean时,自动配置才会生效。这有助于确保不会覆盖用户自定义的事务管理配置。其他相关配置: 在这个自动配置类中,还包括其他一些相关的配置,例如
TransactionInterceptor
的配置和TransactionAttributeSource
的配置等。
总体而言,TransactionAutoConfiguration
的作用是根据类路径下的条件自动配置Spring事务管理。一旦在应用中添加了相关的事务依赖,这个自动配置类就会生效,使得应用具备默认的事务管理功能。如果需要进行定制,可以通过自定义事务管理配置来覆盖或扩展默认的配置。
三 Spring事务不回滚场景
1 未抛出受检异常
spring事务,默认情况下只会回滚RuntimeException
(运行时异常)和Error
(错误),对于普通的Exception(非运行时异常),它不会回滚。 解决方法是使用@Transactional
注解的rollbackFor
属性,明确指定需要回滚的异常类型。
@Transactional(rollbackFor = Exception.class)
public void methodWithTransaction() throws Exception {
// ...
throw new Exception("This will trigger a rollback");
}
2 事务传播机制不当
如果在一个事务内部调用另一个使用不同事务传播机制的方法,可能会导致子事务无法正常回滚。例如,如果外部事务使用REQUIRED
传播行为,而内部事务使用REQUIRES_NEW
传播行为,内部事务的回滚不会影响外部事务。确保事务传播机制符合业务需求。
@Transactional(propagation = Propagation.REQUIRED)
public void outerMethod() {
// ...
innerMethod(); // This method has a different propagation behavior
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void innerMethod() {
// ...
throw new RuntimeException("This will not rollback the outer transaction");
}
或者我们在手动设置propagation参数的时候,把传播特性设置错了,比如:
@Service
public class UserService {
@Transactional(propagation = Propagation.NEVER)
public void add(UserModel userModel) {
saveData(userModel);
updateData(userModel);
}
}
我们可以看到add方法的事务传播特性定义成了Propagation.NEVER,这种类型的传播特性不支持事务,如果有事务则会抛异常。
目前只有这三种传播特性才会创建新事务:REQUIRED,REQUIRES_NEW,NESTED。
3 自己吞了异常
事务不会回滚,最常见的问题是:开发者在代码中手动try...catch了异常。比如:
@Slf4j
@Service
public class UserService {
@Transactional
public void add(UserModel userModel) {
try {
saveData(userModel);
updateData(userModel);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
这种情况下spring事务当然不会回滚,因为开发者自己捕获了异常,又没有手动抛出,换句话说就是把异常吞掉了。
四 嵌套事务
1 嵌套事务回滚多了
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
roleService.doOtherThing();
}
}
@Service
public class RoleService {
@Transactional(propagation = Propagation.NESTED)
public void doOtherThing() {
System.out.println("保存role表数据");
}
}
因为doOtherThing方法出现了异常,没有手动捕获,会继续往上抛,到外层add方法的代理方法中捕获了异常。所以,这种情况是直接回滚了整个事务,不只回滚单个保存点。
可以将内部嵌套事务放在try/catch中,并且不继续往上抛异常。这样就能保证,如果内部嵌套事务中出现异常,只回滚内部事务,而不影响外部事务。
@Slf4j
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
try {
roleService.doOtherThing();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
还有一种情况需要注意,也可能导致回滚多了,那就是doOtherThing
的事务传播是Propagation.REQUIRED
,就算使用了try-catch
也会将外部事务标记为已回滚。这种情况之前在生产问题碰到过,比较坑,感兴趣的可以在网上查看具体原因或者等我下次整理发出一篇新的博客。