@Transactional 是 Spring 框架中用于声明事务的核心注解,而 @Transactional(rollbackFor = Exception.class) 是其带特定属性的使用方式。两者的主要区别在于事务回滚的触发条件不同,具体区别如下:
1. 默认行为(@Transactional)
当使用不带任何属性的 @Transactional 时,Spring 的默认事务默认只在遇到未检查异常(Unchecked Exception) 时才会触发回滚,具体包括:
- 继承自
RuntimeException的异常(如NullPointerException、IllegalArgumentException等) - 继承自
Error的错误(如OutOfMemoryError等)
对于已检查异常(Checked Exception) (即直接继承自 Exception 且非 RuntimeException 的异常,如 IOException、SQLException 等),默认不会触发事务回滚,事务会继续提交。
2. @Transactional(rollbackFor = Exception.class) 的行为
当指定 rollbackFor = Exception.class 时,表示所有继承自 Exception 的异常(包括已检查异常和未检查异常)都会触发事务回滚,具体包括:
- 未检查异常(
RuntimeException及其子类) - 已检查异常(如
IOException、SQLException等)
这是一种更严格的回滚策略,确保任何异常(除了 Error 之外的 Throwable)都能触发回滚(注:Error 本身也会被默认回滚)。
3. 关键区别总结
| 注解形式 | 触发回滚的异常类型 | 不触发回滚的异常类型 |
|---|---|---|
@Transactional |
RuntimeException 及其子类、Error 及其子类 |
所有已检查异常(如 IOException、SQLException 等) |
@Transactional(rollbackFor = Exception.class) |
所有 Exception 及其子类(包括已检查和未检查异常)、Error 及其子类 |
无(几乎覆盖所有业务异常场景) |
4. 何时使用哪种方式?
使用 @Transactional 的场景
适用于只需要在发生运行时异常(通常是程序逻辑错误)时回滚的场景,默认行为更轻量。
使用 @Transactional(rollbackFor = Exception.class) 的场景
适用于需要严格保证数据一致性的场景(如金融、订单等核心业务),确保任何异常(包括业务代码中主动抛出的已检查异常)都能触发回滚,避免数据不一致。
5. 实际开发建议
在实际开发中,推荐显式指定 rollbackFor = Exception.class ,因为业务逻辑中常自定义已检查异常(如 BusinessException),若不指定该属性,这些异常可能导致事务不回滚,引发数据问题。
6. 代码示例对比
默认 @Transactional 示例
java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional // 默认只对RuntimeException回滚
public void updateUserAndLog(Long userId, String newName) throws IOException {
// 1. 更新用户信息
User user = userRepository.findById(userId).orElseThrow();
user.setName(newName);
userRepository.save(user);
// 2. 记录日志(假设这里可能抛出IOException)
if (newName.length() > 50) {
throw new IOException("用户名过长,日志记录失败"); // 已检查异常,默认不回滚
}
// 记录日志的逻辑...
}
}
结果 :如果抛出 IOException,用户信息已更新但日志未记录,事务不会回滚,导致数据不一致。
@Transactional(rollbackFor = Exception.class) 示例
java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional(rollbackFor = Exception.class) // 所有Exception都会回滚
public void updateUserAndLog(Long userId, String newName) throws IOException {
// 1. 更新用户信息
User user = userRepository.findById(userId).orElseThrow();
user.setName(newName);
userRepository.save(user);
// 2. 记录日志(假设这里可能抛出IOException)
if (newName.length() > 50) {
throw new IOException("用户名过长,日志记录失败"); // 已检查异常,会触发回滚
}
// 记录日志的逻辑...
}
}
结果 :如果抛出 IOException,用户信息更新和日志记录都会回滚,保证数据一致性。