先读一下下面这段代码:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MyService {
@Autowired
private UserRepository userRepository; // 假设这是一个Spring Data JPA仓库
@Transactional
public void saveUserWithCatch() {
try {
User user = new User("John", "Doe");
userRepository.save(user); // 第一个数据库操作
// 模拟一个异常
if (true) {
throw new RuntimeException("模拟异常");
}
User anotherUser = new User("Jane", "Doe");
userRepository.save(anotherUser); // 第二个数据库操作
} catch (Exception e) {
// 捕获异常,但不重新抛出
System.out.println("捕获异常: " + e.getMessage());
}
}
}
在saveUserWithCatch
方法中,是否可以维持事务的原子性?即注解 @Transactional
是否生效?答案是否定的。
让我们来看一下 @Transactional 注解的使用注意事项
1、@Transactional 注解使用注意事项
@Transactional 注解是 Spring 框架提供的声明式注解事务解决方案,我们在开发中使用事务保证方法对数据库操作的原子性,要么全部成功,要么全部失败,在使用 @Transactional 注解时,需要注意以下问题:
1、@Transactional 注解只能用在 public 方法上,如果用在 protected 或者 private 的方法上,不会报错,但是该注解不会生效。
2、@Transactional 注解只能回滚非检查型异常(什么是检查型异常,很好理解,在编译之前就可以被编译器识别的异常,即编译器提示代码错误),具体为 RuntimeException 及其子类和 Error 子类,可以从 Spring 源码的 DefaultTransactionAttribute 类里找到判断方法 rollbackOn。
@Override
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
3、检查事务传播行为(propagation behavior)。默认情况下,@Transactional
使用 Propagation.REQUIRED
,这应该适用于大多数情况,但在某些复杂场景下可能需要调整。
4、@Transactional注解不能回滚被 try{} catch() 捕获的异常。
5、@Transactional注解只能对在被 Spring 容器扫描到的类下的方法生效。需要确保@Transactional
注解的方法不是在同一类的其他方法中直接调用,如果在同一类中调用,事务挂历将不会生效。确保通过 Spring 代理调用此方法。
@Service
public class TemplateService {
@Autowired
private TemplateService templateService;
public void outerMethod() {
templateService.saveOrUpdateTemplate(...); // 通过代理调用
}
}
6、确保底层数据库支持事务。例如,某些 NoSQL 数据库可能不支持事务,或者支持有限的事务。
7、多线程操作。确保所有数据库操作在同一个线程中执行,如果有多线程操作,事务管理可能会失效。
其实 Spring 事务的创建也是有一定的规则,对于一个方法里已经存在的事务,Spring 也提供了解决方案去进一步处理存在事务,通过设置 @Tranasctional 的 propagation 属性定义Spring 事务的传播规则。
2、Spring 事务的传播规则
Spring事务的传播行为一共有7种,定义在 spring-tx 模块的 Propagation 枚举类里,对应的常量值定义在 TransactionDefinition 接口里值为 int 类型的 0-6。
以下是 Spring 事务 7 种传播行为的特性:
传播规则 | 描述 | 例子 |
PROPAGATION_REQUIRED | 支持当前事务,如果当前没有事务,则创建一个事务,这是最常用的传播行为,确保方法总是运行在事务中。 | 如果方法 A 调用方法 B,并且方法 A 已经在一个事务中,则方法 B 将加入这个事务。如果方法 A 没有事务,方法 B 将开始一个新事务。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务来执行 | 如果方法 A 调用方法 B,并且方法 A 在一个事务中,则方法 B 也将加入这个事务,如果方法 A 没有事务,方法 B 将以非事务方式加入。 |
PROPAGATION_MANDATORY | 这种模式要求必须在一个现有事务中执行,如果没有事务存在,则抛出 | 如果方法 A 调用方法 B,并且方法 A 没有事务,则方法 B 将抛出异常。 |
PROPAGATION_REQUIRES_NEW | 这种模式确保方法总是运行在一个新的事务中,不管是否存在现有事务 | 如果方法 A 调用方法 B,并且方法 A 已经在一个事务中,则方法 A 的事务将被挂起,方法 B 将开始一个新事务。方法 B 完成后,方法 A 的事务将继续。 |
PROPAGATION_NOT_SUPPORTED | 这种模式确保方法总是以非事务方式执行。 | 如果方法 A 调用方法 B,并且方法 A 已经在一个事务中,则方法 A 的事务将被挂起,方法 B 将以非事务方式执行 |
PROPAGATION_NEVER | 这种模式要求方法不能在事务中执行,如果有现有事务,则抛出 | 如果方法 A 调用方法 B,并且方法 A 已经在一个事务中,则方法 B 将抛出异常 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与 PROPAGATION_REQUIRED 类似的操作。嵌套事务是指一个事务的子事务,它依赖于父事务。 | 如果方法 A 调用方法 B,并且方法 A 在一个事务中,则方法 B 将在这个事务中创建一个嵌套事务。如果方法 A 没有事务,方法 B 将创建一个新事务 |
跟一下 TransactionDefinition 的源码(我的版本是:spring-tx:4.3.15.RELEASE)
跟一下,getPropagationBehavior(); 方法:
可以看到,默认事务就是 PROPAGATION_REQUIRED
可以进入到 AbstractPlatformTransactionManager
类中,跟一下 getTransaction(@NullableTransactionDefinition definition)
方法,方法内部就是 spring 获取事务的逻辑:
如果事务存在,那么就交由 handleExistingTransaction
方法处理:
感兴趣的小伙伴可以研究一下源码,但在实际开发中,我们只需要在 service 类上标记 @Transactional 注解,类下的方法都能根据指定的异常进行事务回滚,但切记要注意 @Transactional 的有效范围。