本文目录导读:

Java事务回滚的规范流程,核心在于确保资源被正确释放、异常被妥善处理,并且回滚条件清晰明确。
事务管理分为编程式事务(手动控制)和声明式事务(基于AOP注解),下面从这两种方式出发,结合最佳实践,总结规范流程。
核心原则
- 明确边界:事务开始和结束的边界必须清晰,通常是“Service层”。
- 异常驱动:默认情况下,运行时异常(RuntimeException及其子类) 触发回滚,受检异常(Exception的子类,非RuntimeException)默认不触发回滚。
- 资源管理:数据库连接、会话等资源必须在finally块或使用try-with-resources确保关闭。
- 幂等与状态:失败回滚后,业务流程应能安全重试,避免脏数据残留。
声明式事务(最常用,推荐)
通过@Transactional注解,由Spring AOP自动管理事务的提交和回滚。
规范流程
- 在Service层(而非Controller层)开启事务。
- 设置合理的rollbackFor,明确哪些异常需要回滚。
- 在方法内部捕获异常并处理时,若需要触发回滚,需重新抛出注解指定的异常。
- 避免事务嵌套陷阱(如
try-catch吞掉了异常)。
示例代码
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private AccountDao accountDao;
/**
* 创建订单并扣减库存
*
* @param order 订单信息
* @throws BusinessException 业务异常(受检异常,不会自动回滚)
*/
@Transactional(rollbackFor = Exception.class) // 明确指定:所有Exception都回滚
public void createOrder(Order order) throws BusinessException {
LogUtil.info("开始事务:创建订单");
try {
// 1. 插入订单记录(操作数据库)
orderDao.insert(order);
// 2. 扣减库存(可能抛RuntimeException,如除零、空指针等)
stockService.deductStock(order.getProductId(), order.getQuantity());
// 3. 调用外部服务(可能抛受检异常)
// 注意:这里如果捕获异常,必须重新抛出,否则事务不会回滚
boolean result = externalService.syncToERP(order);
if (!result) {
// 抛出受检异常,但因为rollbackFor=Exception.class,会回滚
throw new BusinessException("同步ERP失败");
}
// 4. 事务正常结束,自动提交
LogUtil.info("事务提交");
} catch (Exception e) {
// 记录日志,并重新抛出(让AOP感知到异常,触发回滚)
LogUtil.error("事务执行失败,即将回滚", e);
throw e; // 关键!不能吞掉异常
}
}
}
声明式事务回滚的三种正确写法
| 场景 | 推荐做法 | 说明 |
|---|---|---|
| 运行时异常回滚 | 不捕获异常,或捕获后重新抛出 | 默认rollbackFor=RuntimeException |
| 受检异常回滚 | 注解上设置rollbackFor=Exception.class |
同时注意捕获后重新抛出 |
| 局部不执行回滚 | 使用@Transactional(noRollbackFor=...) |
明确指定某些异常不触发回滚(不常用) |
常见错误:事务不回滚
// ❌ 错误:吞掉了异常,Spring感知不到失败,会正常提交
@Transactional
public void createOrder(Order order) {
try {
orderDao.insert(order);
stockService.deductStock(order.getProductId(), order.getQuantity());
} catch (Exception e) {
LogUtil.error("出错了但我不抛出,事务不会回滚", e);
// 没有throw e,相当于告诉Spring:一切正常,提交!
}
}
编程式事务(手动控制)
适用于需要细粒度控制、或者远程调用、异步场景。
规范流程
- 获取事务管理器(PlatformTransactionManager 或 TransactionTemplate)。
- 定义事务属性(隔离级别、传播行为、回滚规则)。
- 手动commit或rollback。
- 务必确保commit/rollback只执行一次,且资源被释放。
示例代码(使用TransactionTemplate - 推荐)
TransactionTemplate是模板方法模式,内部自动处理了try-catch-finally和回滚逻辑。
@Service
public class PaymentService {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private PaymentDao paymentDao;
public void processPayment(Payment payment) {
LogUtil.info("开始编程式事务");
// 定义执行逻辑
PaymentExecuteResult result = transactionTemplate.execute(new TransactionCallback<PaymentExecuteResult>() {
@Override
public PaymentExecuteResult doInTransaction(TransactionStatus status) {
try {
// 1. 数据库操作
paymentDao.insert(payment);
// 2. 业务逻辑
// ...
// 3. 如果业务判定需要回滚,可以手动调用
if (someConditionFailed) {
status.setRollbackOnly(); // 标记为只回滚
}
return new PaymentExecuteResult(true, "成功");
} catch (Exception e) {
// 异常发生时,TransactionTemplate会自动调用status.setRollbackOnly()
LogUtil.error("编程式事务执行失败", e);
// 如果return null,则事务会回滚(因为抛出异常)
// 如果catch后不重新抛出,需要手动setRollbackOnly
status.setRollbackOnly(); // 显式标记回滚
return new PaymentExecuteResult(false, "失败: " + e.getMessage());
}
}
});
LogUtil.info("事务执行结果:{}", result);
}
}
手动获取事务管理器(不常用,仅用于特殊场景)
@Autowired
private PlatformTransactionManager transactionManager;
// 定义事务定义
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
def.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
def.setTimeout(30); // 超时设置
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 业务逻辑
// ...
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
// 不需要finally中重复rollback,commit或rollback只能执行一次
事务回滚的规范检查清单
每一项都很关键,建议在代码审查时重点关注:
- 异常处理:是否吞没了异常?(
catch(Exception e)中没有throw e或TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()) - 注解位置:
@Transactional是否放在Service层的方法上?(Controller层开启事务是反模式) - 传播行为:内部方法调用是否使用了
@Transactional(propagation=REQUIRES_NEW)导致子事务独立? - 资源关闭:数据库连接、文件流是否在 finally 块中关闭?或者使用了
try-with-resources? - 大事务拆分:单个事务内是否包含了耗时操作(如网络IO、文件下载)?如果包含,会长时间占用数据库连接,增加死锁和超时风险。
- 嵌套事务:是否存在
ServiceA.methodA()中调用ServiceB.methodB(),且两个方法都有@Transactional?methodB抛异常,methodA的事务是否回滚,取决于传播行为。 - 测试验证:是否有单元测试或手动测试验证异常场景下数据未被修改?
典型错误与正确做法对比
| 场景 | ❌ 错误做法 | ✅ 正确做法 |
|---|---|---|
| 捕获异常后不抛出 | try {} catch (Exception e) {log.error();} |
catch (Exception e) {log.error(); throw e;} 或 status.setRollbackOnly() |
| 受检异常不回滚 | @Transactional 不设置 rollbackFor |
@Transactional(rollbackFor=Exception.class) |
| 在Controller层开启事务 | @Transactional 放在 @RequestMapping 方法上 |
移到 Service层方法上 |
| 事务内包含远程调用 | @Transactional 包裹 httpClient.doPost() |
将远程调用移出事务,或使用编程式事务控制回滚范围 |
| 多数据源 | 只配置了一个事务管理器 | 配置 @Transactional("dataSource1TransactionManager") 指定对应管理器 |
规范的Java事务回滚流程可以概括为:
- 使用声明式事务(
@Transactional),放置在Service层方法上。 - 明确指定回滚异常类型(特别是受检异常,需要设置
rollbackFor)。 - 在业务逻辑中,如果捕获了异常,必须重新抛出或将事务标记为
rollbackOnly,否则事务不会回滚。 - 使用
TransactionTemplate处理编程式事务,避免手动commit/rollback带来的遗漏风险。 - 避免事务内包含耗时操作,防止长事务。
- 每次修改后,通过交易验证(回滚后数据状态恢复)来确保正确性。
遵循这些规范,可以显著减少因事务回滚不一致导致的数据错误。