nested嵌套事务

主事务可以改成注册会员,嵌套事务为发送短信。

如果不进行try catch 则nested失效,事务会整体回滚。try catch之后会进行保存点机制,只回滚嵌套事务里面的语句。

# 嵌套事务(NESTED)详细示例

嵌套事务是 Spring 事务传播机制中比较特殊的一种,它使用**保存点(Savepoint)** 来实现部分回滚。

## 1. **基础概念回顾**
- **NESTED**:如果当前存在事务,则在嵌套事务(保存点)中执行
- **特点**:嵌套事务是外层事务的一部分,可以独立回滚,但外层事务回滚会导致嵌套事务也回滚
- **数据库要求**:需要数据库支持保存点(MySQL InnoDB、Oracle、PostgreSQL 等支持)

## 2. **完整代码示例**

### 2.1 实体类
```java
@Entity
public class Account {
@Id
private Long id;
private String accountNumber;
private BigDecimal balance;

// getters & setters
}

@Entity
public class TransferRecord {
@Id
@GeneratedValue
private Long id;
private String fromAccount;
private String toAccount;
private BigDecimal amount;
private LocalDateTime transferTime;

// getters & setters
}
```

### 2.2 服务层 - 银行转账场景
```java
@Service
public class BankTransferService {

@Autowired
private AccountRepository accountRepository;

@Autowired
private TransferRecordRepository transferRecordRepository;

/**
* 外层事务:完整转账流程
*/
@Transactional
public void transferMoney(String fromAccNum, String toAccNum, BigDecimal amount) {
System.out.println("=== 开始转账流程 ===");
System.out.println("外层事务ID: " + TransactionSynchronizationManager.getCurrentTransactionName());

try {
// 步骤1:扣减转出账户金额(主事务操作)
deductFromAccount(fromAccNum, amount);

// 步骤2:增加转入账户金额(嵌套事务)
addToAccountWithNested(toAccNum, amount);

// 步骤3:记录转账记录(另一个嵌套事务)
createTransferRecord(fromAccNum, toAccNum, amount);

System.out.println("=== 转账成功 ===");

} catch (InsufficientBalanceException e) {
System.out.println("余额不足,整个转账回滚");
throw e; // 外层事务回滚
} catch (AccountNotFoundException e) {
System.out.println("账户不存在,整个转账回滚");
throw e; // 外层事务回滚
} catch (DailyLimitExceededException e) {
// 这里可以处理:只回滚步骤2,保留步骤1和步骤3
System.out.println("超过每日限额,但已记录日志");
// 不抛出异常,让外层事务继续提交
}
}

/**
* 扣款操作 - 主事务的一部分
*/
private void deductFromAccount(String accountNum, BigDecimal amount) {
Account account = accountRepository.findByAccountNumber(accountNum);
if (account == null) {
throw new AccountNotFoundException("账户不存在: " + accountNum);
}

if (account.getBalance().compareTo(amount) < 0) {
throw new InsufficientBalanceException("余额不足");
}

account.setBalance(account.getBalance().subtract(amount));
accountRepository.save(account);
System.out.println("扣款成功: " + accountNum + " 扣除 " + amount);
}

/**
* 存款操作 - 嵌套事务(可以独立回滚)
*/
@Transactional(propagation = Propagation.NESTED)
public void addToAccountWithNested(String accountNum, BigDecimal amount) {
System.out.println("进入嵌套事务 - 存款操作");
System.out.println("当前保存点: " + TransactionSynchronizationManager.getCurrentTransactionName());

Account account = accountRepository.findByAccountNumber(accountNum);
if (account == null) {
throw new AccountNotFoundException("转入账户不存在: " + accountNum);
}

// 模拟业务规则:每日存款限额检查
BigDecimal todayTotal = calculateTodayDepositTotal(accountNum);
if (todayTotal.add(amount).compareTo(new BigDecimal("100000")) > 0) {
// 超过每日限额,抛出特定异常
throw new DailyLimitExceededException("超过每日存款限额");
}

account.setBalance(account.getBalance().add(amount));
accountRepository.save(account);
System.out.println("存款成功: " + accountNum + " 存入 " + amount);
}

/**
* 记录转账日志 - 另一个嵌套事务
*/
@Transactional(propagation = Propagation.NESTED)
public void createTransferRecord(String fromAccNum, String toAccNum, BigDecimal amount) {
System.out.println("进入嵌套事务 - 记录转账日志");

TransferRecord record = new TransferRecord();
record.setFromAccount(fromAccNum);
record.setToAccount(toAccNum);
record.setAmount(amount);
record.setTransferTime(LocalDateTime.now());

transferRecordRepository.save(record);
System.out.println("转账记录已保存");
}

private BigDecimal calculateTodayDepositTotal(String accountNum) {
// 模拟计算今日已存款总额
return new BigDecimal("50000"); // 假设今日已存5万
}
}
```

### 2.3 异常类定义
```java
public class InsufficientBalanceException extends RuntimeException {
public InsufficientBalanceException(String message) {
super(message);
}
}

public class AccountNotFoundException extends RuntimeException {
public AccountNotFoundException(String message) {
super(message);
}
}

public class DailyLimitExceededException extends RuntimeException {
public DailyLimitExceededException(String message) {
super(message);
}
}
```

## 3. **不同场景的执行结果**

### 场景1:正常流程
```java
@RunWith(SpringRunner.class)
@SpringBootTest
public class BankTransferTest {

@Autowired
private BankTransferService transferService;

@Test
public void testSuccessfulTransfer() {
// 转出账户余额: 20000,转入账户今日已存: 50000
transferService.transferMoney("ACC001", "ACC002", new BigDecimal("1000"));

// 结果:
// === 开始转账流程 ===
// 扣款成功: ACC001 扣除 1000
// 进入嵌套事务 - 存款操作
// 存款成功: ACC002 存入 1000
// 进入嵌套事务 - 记录转账日志
// 转账记录已保存
// === 转账成功 ===
// 所有操作都提交
}
}
```

### 场景2:转入账户不存在
```java
@Test
public void testToAccountNotFound() {
try {
transferService.transferMoney("ACC001", "ACC999", new BigDecimal("1000"));
} catch (AccountNotFoundException e) {
System.out.println(e.getMessage());
}

// 结果:
// === 开始转账流程 ===
// 扣款成功: ACC001 扣除 1000
// 进入嵌套事务 - 存款操作
// 抛出 AccountNotFoundException
// 整个事务回滚(包括扣款操作)
// ACC001的余额恢复
}
```

### 场景3:超过每日存款限额
```java
@Test
public void testDailyLimitExceeded() {
// 转账金额 60000,加上今日已存50000,超过10万限额
transferService.transferMoney("ACC001", "ACC002", new BigDecimal("60000"));

// 结果:
// === 开始转账流程 ===
// 扣款成功: ACC001 扣除 60000
// 进入嵌套交易 - 存款操作
// 抛出 DailyLimitExceededException
// 外层事务捕获异常,不继续抛出
// 进入嵌套事务 - 记录转账日志
// 转账记录已保存
// === 转账成功 ===
// 最终结果:
// - ACC001扣款成功(已提交)
// - ACC002存款失败(嵌套事务回滚)
// - 转账记录已保存(已提交)
// 转账失败但有完整日志记录
}
```

## 4. **数据库层面发生了什么**

### 4.1 SQL 执行顺序
```sql
-- 外层事务开始
BEGIN;

-- 1. 扣款操作(主事务)
UPDATE accounts SET balance = balance - 1000 WHERE account_number = 'ACC001';

-- 2. 创建保存点(嵌套事务开始)
SAVEPOINT nested_savepoint_1;

-- 存款操作尝试
UPDATE accounts SET balance = balance + 1000 WHERE account_number = 'ACC002';

-- 如果存款失败,回滚到保存点
ROLLBACK TO SAVEPOINT nested_savepoint_1;

-- 3. 另一个保存点(记录日志)
SAVEPOINT nested_savepoint_2;

INSERT INTO transfer_records (...) VALUES (...);

-- 所有成功,提交外层事务
COMMIT;
```

### 4.2 保存点机制图解
```
外层事务开始
|
|--- 主事务操作1: 扣款 (不可部分回滚)
|
|--- 嵌套事务1开始 (保存点SP1)
| |
| |--- 存款操作尝试
| |
| |--- 如果失败: 回滚到SP1
| |--- 如果成功: 继续
|
|--- 嵌套事务2开始 (保存点SP2)
| |
| |--- 记录日志
|
|--- 如果外层事务失败: 全部回滚
|
|--- 如果外层事务成功: 整体提交
```

## 5. **与 REQUIRES_NEW 的对比示例**

```java
@Service
public class ComparisonService {

@Autowired
private OrderService orderService;

/**
* 使用 NESTED 的场景
*/
@Transactional
public void processOrderWithNested(Order order) {
// 主操作:创建订单
orderService.createOrder(order);

try {
// 嵌套事务:扣减库存
orderService.deductInventoryNested(order.getItems());
} catch (InventoryException e) {
// 库存不足,只回滚库存操作
// 订单记录仍然保留,可以后续处理
log.warn("库存不足,但订单已创建: {}", order.getId());
}

// 继续其他操作...
}

/**
* 使用 REQUIRES_NEW 的场景
*/
@Transactional
public void processOrderWithRequiresNew(Order order) {
// 主操作:创建订单
orderService.createOrder(order);

try {
// 独立事务:发送消息
notificationService.sendNotificationNew(order);
// 即使订单后续失败,通知已发送且不会回滚
} catch (NotificationException e) {
log.error("通知发送失败,但继续处理订单");
}

// 继续其他操作...
}
}

@Service
public class OrderService {

@Transactional(propagation = Propagation.NESTED)
public void deductInventoryNested(List<OrderItem> items) {
// 库存扣减,可以独立回滚
items.forEach(item -> inventoryDao.deduct(item));
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendNotificationNew(Order order) {
// 发送通知,完全独立的事务
notificationDao.save(new Notification(order));
externalNotificationService.send(order);
}
}
```

## 6. **实际应用场景建议**

### 适用 NESTED 的场景:
1. **部分操作可失败**:如库存扣减失败,但订单仍需保留
2. **复杂业务流**:多步骤操作,某些步骤可回滚
3. **需要原子性但允许部分回滚**:整体要么全成功,要么全回滚,但中间步骤可独立回滚

### 不适用 NESTED 的场景:
1. **数据库不支持保存点**:某些数据库或存储引擎不支持
2. **需要完全隔离**:内层操作需要独立提交,不受外层影响
3. **JPA/Hibernate 复杂操作**:某些 JPA 操作可能不支持保存点

### 最佳实践:
```java
@Transactional
public void complexBusinessProcess() {
// 步骤1:核心操作(必须成功)
saveMainEntity();

try {
// 步骤2:次要操作(可失败)
processOptionalTaskNested();
} catch (BusinessException e) {
// 记录日志,继续执行
log.warn("Optional task failed, but continuing", e);
}

try {
// 步骤3:另一个次要操作
processAnotherTaskNested();
} catch (BusinessException e) {
log.warn("Another task failed", e);
}

// 步骤4:最终操作(必须成功)
completeProcess();
}
```

## 7. **注意事项**
1. **异常处理**:嵌套事务的异常默认会传播到外层事务
2. **性能**:保存点有一定开销,但比新建事务小
3. **锁机制**:同一连接,避免死锁
4. **连接管理**:复用连接,节省资源
5. **测试**:务必测试各种异常场景

嵌套事务提供了灵活的部分回滚能力,特别适合复杂业务流程中需要细粒度事务控制的场景。

posted @ 2026-01-11 05:00  人在代码在  阅读(1)  评论(0)    收藏  举报