Java事务回滚案例详解
基本概念
事务回滚是指当程序执行过程中发生异常或错误时,将数据库恢复到事务开始前的状态,保证数据的一致性和完整性。

常见回滚场景案例
转账操作(经典案例)
@Service
public class TransferService {
@Autowired
private AccountMapper accountMapper;
/**
* 转账操作
* @param fromAccountId 转出账户
* @param toAccountId 转入账户
* @param amount 转账金额
*/
@Transactional(rollbackFor = Exception.class)
public void transfer(Long fromAccountId, Long toAccountId, BigDecimal amount) {
// 1. 检查转出账户余额
Account fromAccount = accountMapper.selectById(fromAccountId);
if (fromAccount.getBalance().compareTo(amount) < 0) {
throw new RuntimeException("余额不足");
}
// 2. 扣减转出账户余额
fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
accountMapper.updateById(fromAccount);
// 3. 增加转入账户余额(这里可能出现异常)
Account toAccount = accountMapper.selectById(toAccountId);
if (toAccount == null) {
throw new RuntimeException("转入账户不存在");
}
toAccount.setBalance(toAccount.getBalance().add(amount));
accountMapper.updateById(toAccount);
// 4. 记录转账日志
transferLogService.saveLog(fromAccountId, toAccountId, amount);
}
}
批量数据处理
@Service
public class BatchProcessService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockMapper stockMapper;
/**
* 批量处理订单并扣减库存
*/
@Transactional(rollbackFor = Exception.class)
public void processOrders(List<OrderDTO> orders) {
for (OrderDTO order : orders) {
try {
// 处理单个订单
processSingleOrder(order);
} catch (Exception e) {
log.error("处理订单失败,订单号: {}", order.getOrderNo(), e);
// 记录失败原因,继续处理其他订单
order.setStatus("FAIL");
order.setErrorMsg(e.getMessage());
// 可选:将失败订单记录到单独的表
failedOrderService.saveFailedOrder(order);
}
}
}
private void processSingleOrder(OrderDTO order) {
// 更新订单状态
order.setStatus("PROCESSING");
orderMapper.updateStatus(order);
// 扣减库存
Stock stock = stockMapper.selectByProductId(order.getProductId());
if (stock.getQuantity() < order.getQuantity()) {
throw new RuntimeException("库存不足");
}
stock.setQuantity(stock.getQuantity() - order.getQuantity());
stockMapper.updateById(stock);
// 模拟其他业务处理
otherBusinessProcess(order);
}
}
手动回滚(编程式事务)
@Service
public class ManualRollbackService {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private UserMapper userMapper;
@Autowired
private RoleMapper roleMapper;
/**
* 使用编程式事务实现手动回滚
*/
public void createUserWithRoles(String userName, List<String> roleIds) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
// 1. 创建用户
User user = new User();
user.setName(userName);
userMapper.insert(user);
// 2. 为用户分配角色
for (String roleId : roleIds) {
UserRole userRole = new UserRole();
userRole.setUserId(user.getId());
userRole.setRoleId(roleId);
userRoleMapper.insert(userRole);
}
// 模拟业务校验失败
if (roleIds.size() > 5) {
// 手动回滚
status.setRollbackOnly();
throw new RuntimeException("用户角色数量不能超过5个");
}
} catch (Exception e) {
log.error("创建用户失败", e);
status.setRollbackOnly();
throw e;
}
}
});
}
}
回滚配置详解
@Transactional 常用参数
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {
// 指定事务管理器
String value() default "";
// 回滚的异常类型
Class<? extends Throwable>[] rollbackFor() default {};
// 不回滚的异常类型
Class<? extends Throwable>[] noRollbackFor() default {};
// 事务传播行为
Propagation propagation() default Propagation.REQUIRED;
// 事务隔离级别
Isolation isolation() default Isolation.DEFAULT;
// 超时时间
int timeout() default -1;
// 是否只读事务
boolean readOnly() default false;
}
回滚失效场景及解决方案
@Service
public class RollbackFailureCases {
@Autowired
private UserMapper userMapper;
/**
* 错误示例1:异常被捕获,事务不会回滚
*/
@Transactional
public void wrongCase1() {
try {
// 业务操作
userMapper.insert(new User("test1"));
// 模拟异常
int i = 1/0;
} catch (Exception e) {
// 异常被捕获,事务不会回滚
log.error("发生异常,但不会回滚", e);
}
// 事务会在方法结束时提交,前面的插入操作会被保存
}
/**
* 错误示例2:内部调用导致事务失效
*/
@Transactional
public void wrongCase2() {
// 调用本类方法不会开启新事务
this.updateUser();
// 下面的操作如果发生异常,updateUser中已提交的操作不会回滚
int i = 1/0;
}
/**
* 正确做法:事务方法调用
*/
@Service
public static class UserService {
@Transactional
public void correctCase() {
// 调用其他类的方法可以开启新事务
userService2.updateUser();
// 下面的操作发生异常,updateUser中的操作也会回滚
int i = 1/0;
}
}
}
/**
* 解决方案
*/
@Service
public class RollbackSolutionService {
@Autowired
private SelfProxy selfProxy;
@Transactional(rollbackFor = Exception.class)
public void correctMethod() {
// 1. 捕获异常后重新抛出
try {
// 业务操作
userMapper.insert(new User("test1"));
int i = 1/0;
} catch (Exception e) {
log.error("业务处理失败", e);
throw new RuntimeException("业务处理失败", e); // 重新抛出异常
}
}
/**
* 使用代理调用方法解决自调用问题
*/
@Transactional
public void useProxy() {
// 通过代理调用
selfProxy.updateUser();
// 后续操作
int i = 1/0;
}
}
常见回滚异常测试
@SpringBootTest
public class RollbackTest {
@Autowired
private TransferService transferService;
@Test
public void testTransferRollback() {
BigDecimal originalFromBalance = accountMapper.selectById(1L).getBalance();
BigDecimal originalToBalance = accountMapper.selectById(2L).getBalance();
try {
transferService.transfer(1L, 2L, new BigDecimal("1000"));
} catch (Exception e) {
// 验证转账失败后余额不变
BigDecimal afterFromBalance = accountMapper.selectById(1L).getBalance();
BigDecimal afterToBalance = accountMapper.selectById(2L).getBalance();
assertEquals(originalFromBalance, afterFromBalance);
assertEquals(originalToBalance, afterToBalance);
}
}
}
最佳实践建议
- 合理设置 rollbackFor:建议设置为
rollbackFor = Exception.class - 避免事务过大:将大事务拆分为小事务
- 避免在事务中做耗时操作:如网络请求、文件操作等
- 正确处理受检异常:默认不回滚,需提前抛出或转换
- 注意事务失效场景:自调用、方法非public、类未被Spring管理等情况
这个案例涵盖了Java事务回滚的主要场景和常见问题解决方案,在实际开发中,要根据具体业务需求选择合适的事务处理方式。