requires_new 与required的区别
这里的这里的代码Propagation.REQUIRED如果 throw new InsufficientBalanceException("余额不足");整体都会回滚。
如果是Propagation.REQUIRES,则只会回滚新事务里面的语句。
@Service
public class BankService {
@Autowired
private TransferService transferService;
@Transactional
public void methodA(String fromAccount, String toAccount, BigDecimal amount) {
// 操作1:记录转账请求日志(重要,必须记录)
logService.logTransferRequest(fromAccount, toAccount, amount);
try {
// 操作2:执行核心转账(可能失败,需要独立事务)
transferService.methodB(fromAccount, toAccount, amount);
} catch (Exception e) {
// 捕获B的异常,不让它影响A的继续执行
System.out.println("转账失败,但继续执行A的后续操作");
// 这里可以记录监控、通知等,但A事务继续
}
// 操作3:发放转账积分(营销活动,即使转账失败也发积分鼓励用户)
rewardService.giveTransferPoints(fromAccount);
}
}
@Service
public class TransferService {
@Transactional(propagation = Propagation.REQUIRED)
public void methodB(String fromAccount, String toAccount, BigDecimal amount) {
// 这里模拟转账业务逻辑
// 1. 检查账户余额是否充足
if (!checkBalance(fromAccount, amount)) {
// 余额不足,抛异常!
throw new InsufficientBalanceException("余额不足");
}
// 2. 扣款
accountDao.deduct(fromAccount, amount);
// 3. 存款
accountDao.add(toAccount, amount);
}
}
**是的,整个事务都会回滚!**
## 原因分析
在你的代码中,`methodB`使用的是 `Propagation.REQUIRED`(这也是默认值):
```java
@Transactional(propagation = Propagation.REQUIRED) // 或直接 @Transactional
public void methodB(...)
```
### 执行流程:
1. **调用methodA时**,Spring开启事务 **Tx**
2. **调用methodB时**,由于是`REQUIRED`:
- 检测到当前已有事务 Tx
- **methodB直接加入Tx,共享同一个事务**
3. **methodB抛出异常**时:
- 异常在methodB内部**没有被捕获**
- 异常沿着调用栈向上传播到methodA
4. **methodA虽然try-catch了异常**:
```java
try {
transferService.methodB(...); // 这里抛出异常
} catch (Exception e) { // 这里捕获到了
System.out.println("转账失败...");
}
```
- 异常确实被methodA的catch块捕获了
- **但事务已经在异常抛出时被标记为回滚了!**
### 关键问题:
**Spring的事务回滚机制在异常被抛到@Transactional方法边界时就已经触发**,不是在catch块执行时才决定。
更具体地说:
1. methodB抛出的`InsufficientBalanceException`传播到了**methodB的@Transactional注解边界**
2. Spring事务拦截器检测到这个异常,**立即将当前事务(Tx)标记为"rollback-only"**
3. 然后异常继续向上传播到methodA的catch块
4. 即使methodA捕获了异常,**事务状态已经是"必须回滚"**
5. 当methodA执行完毕,Spring尝试提交事务时,发现事务已被标记为rollback-only,**强制回滚整个事务**
---
## 验证结果
假设执行你的代码:
```java
// 事务 Tx 开始
logService.logTransferRequest(...); // 成功
transferService.methodB(...); // 抛出异常,事务标记为回滚
// catch块执行,打印日志
rewardService.giveTransferPoints(...); // 成功执行,但在回滚的事务中
// methodA结束,Spring提交事务时发现需要回滚
// 整个Tx回滚!
```
**最终结果:**
- ❌ 日志记录被回滚(未保存)
- ❌ 转账操作被回滚(未执行)
- ❌ 积分发放被回滚(未保存)
- 用户看到"转账失败,但继续执行A的后续操作"的日志,**但数据库里什么都没留下**
---
## 如何让B回滚而A不回滚?
有几种解决方案:
### 方案1:使用REQUIRES_NEW(推荐)
```java
@Service
public class TransferService {
@Transactional(propagation = Propagation.REQUIRES_NEW) // 独立事务!
public void methodB(...) {
// ...
}
}
```
### 方案2:在B方法内捕获异常(不推荐,破坏事务语义)
```java
@Transactional(propagation = Propagation.REQUIRED)
public void methodB(String fromAccount, String toAccount, BigDecimal amount) {
try {
if (!checkBalance(fromAccount, amount)) {
throw new InsufficientBalanceException("余额不足");
}
accountDao.deduct(fromAccount, amount);
accountDao.add(toAccount, amount);
} catch (Exception e) {
// 在事务方法内部捕获异常,不抛出
// 返回错误码或记录日志
log.error("转账失败", e);
// 但这样事务不会回滚!可能有数据不一致问题
}
}
```
### 方案3:使用编程式事务管理
```java
public void methodA(...) {
// 使用TransactionTemplate等编程式控制
}
```
### 方案4:指定不回滚的异常类型
```java
@Service
public class BankService {
@Transactional(noRollbackFor = InsufficientBalanceException.class) // 指定此异常不回滚
public void methodA(...) {
// ...
transferService.methodB(...); // 如果抛出InsufficientBalanceException,A事务不回滚
// ...
}
}
@Service
public class TransferService {
@Transactional(propagation = Propagation.REQUIRED)
public void methodB(...) throws InsufficientBalanceException { // 声明抛出特定异常
// ...
}
}
```
---
## 重要原则
1. **同一个事务中,任何未捕获的RuntimeException都会导致整个事务回滚**
2. **事务回滚的标记发生在异常到达@Transactional方法边界时**,不是在最终提交时
3. **REQUIRED传播行为创建的是"all-or-nothing"的事务**:要么全部成功,要么全部回滚
4. **如果要实现部分成功/部分失败**,需要将可能失败的操作放在独立事务中(如REQUIRES_NEW)
所以你的代码中,使用`REQUIRED`时,B的异常一定会导致A和B一起回滚,无论A是否catch这个异常。

浙公网安备 33010602011771号