“Transactional 不是套个注解就万事大吉的”会议室里小李指着白板上的一段代码语气激动“我们这个订单服务里外层方法加了 Transactional内层又调了一个带 REQUIRES_NEW 的子方法结果事务没回滚数据不一致了”老王皱眉“你是不是没看 Spring 的事务传播机制REQUIRES_NEW 会挂起当前事务新建一个但如果你外层没正确处理异常内层提交后外层回滚那不就脏数据了”“我查了文档说 REQUIRES_NEW 会独立提交啊……”“文档没错但你没看源码不知道‘挂起’到底是怎么实现的。”我敲了敲桌子“今天咱们不靠猜直接从 Spring 源码走一遍看看 Transactional 在嵌套调用时事务上下文是怎么流转的。”需求约束订单履约中的事务一致性挑战我们的业务场景是典型的订单履约系统用户下单后系统需要完成库存扣减、积分扣除、优惠券核销、物流创建等一系列操作。这些操作必须保证原子性——要么全成功要么全回滚。初期方案很简单在OrderService.createOrder()方法上加Transactional内部依次调用inventoryService.deduct()、pointsService.deduct()、couponService.use()。但随着业务复杂化出现了新的需求积分扣除需要独立审计日志即使主订单失败积分操作也要记录用于对账优惠券核销必须立即生效不能因其他服务失败而回滚物流创建是异步的但必须保证最终一致性。于是团队引入了Propagation.REQUIRES_NEW试图让某些子操作“独立提交”。但上线后却频繁出现数据不一致积分扣了、订单没生成优惠券用了、库存没减。问题出在哪我们决定从源码层面拆解 Spring 的事务管理机制。架构设计Spring 事务管理的核心组件Spring 的事务管理基于 AOP 实现核心组件包括TransactionInterceptor事务切面负责在方法前后开启/提交/回滚事务PlatformTransactionManager事务管理器接口如DataSourceTransactionManagerTransactionStatus事务状态对象记录当前事务是否为新事务、是否已完成等TransactionSynchronizationManager线程本地存储ThreadLocal管理事务资源如 Connection、同步器等。关键点在于事务的“挂起”与“恢复”是通过 ThreadLocal 实现的。关键代码/组件从误用到正确的演进错误方案盲目使用 REQUIRES_NEWService public class OrderService { Autowired private InventoryService inventoryService; Autowired private PointsService pointsService; Transactional public void createOrder(Order order) { inventoryService.deduct(order.getSkuId(), order.getQuantity()); pointsService.deduct(order.getUserId(), order.getPoints()); // 使用 REQUIRES_NEW // 其他操作... } } Service public class PointsService { Transactional(propagation Propagation.REQUIRES_NEW) public void deduct(Long userId, Integer points) { // 扣除积分并记录审计日志 pointsRepository.deduct(userId, points); auditLogRepository.log(userId, DEDUCT, points); } }表面看逻辑清晰但问题在于外层事务异常时内层已提交无法回滚。更隐蔽的问题是如果外层事务在pointsService.deduct()执行期间被挂起但后续代码抛出异常外层事务回滚而内层事务已提交导致数据不一致。源码走读TransactionInterceptor 如何处理传播行为我们进入TransactionInterceptor.invoke()方法核心逻辑如下protected Object invokeWithinTransaction(Method method, Class? targetClass, final InvocationCallback invocation) { TransactionAttributeSource tas getTransactionAttributeSource(); final TransactionAttribute txAttr (tas ! null ? tas.getTransactionAttribute(method, targetClass) : null); final PlatformTransactionManager tm determineTransactionManager(txAttr); final String joinpointIdentification methodIdentification(method, targetClass, txAttr); if (txAttr ! null tm instanceof CallbackPreferringPlatformTransactionManager) { // 省略回调逻辑 } else { TransactionInfo txInfo createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal null; try { retVal invocation.proceedWithInvocation(); } catch (Throwable ex) { completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); } commitTransactionAfterReturning(txInfo); return retVal; } }重点在createTransactionIfNecessary()方法。我们深入查看其对REQUIRES_NEW的处理protected TransactionInfo createTransactionIfNecessary(PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) { if (txAttr ! null txAttr.getName() null) { txAttr new DelegatingTransactionAttribute(txAttr) { Override public String getName() { return joinpointIdentification; } }; } TransactionStatus status null; if (txAttr ! null) { if (txAttr.isReadOnly()) { status tm.getTransaction(txAttr); } else { status tm.getTransaction(txAttr); } } return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); }继续进入AbstractPlatformTransactionManager.getTransaction()public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException { Object transaction doGetTransaction(); if (isExistingTransaction(transaction) definition.getPropagationBehavior() ! TransactionDefinition.PROPAGATION_REQUIRES_NEW) { // 当前有事务且不是 REQUIRES_NEW则加入现有事务 return handleExistingTransaction(definition, transaction, debugEnabled); } // 当前无事务或 propagation REQUIRES_NEW SuspendedResourcesHolder suspendedResources suspend(null); boolean newSynchronization (getTransactionSynchronization() ! SYNCHRONIZATION_NEVER); DefaultTransactionStatus status newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); doBegin(transaction, definition); prepareSynchronization(status, definition); return status; }关键来了当传播行为为REQUIRES_NEW时Spring 会调用suspend(null)挂起当前事务。suspend()方法源码protected final SuspendedResourcesHolder suspend(Object transaction) throws TransactionException { if (currentTransaction ! null) { // 挂起当前事务资源Connection、同步器等 Object resumed doSuspend(transaction); // 清除 ThreadLocal 中的事务上下文 TransactionSynchronizationManager.setSynchronizationOnCurrentThread(false); TransactionSynchronizationManager.bindResource(getDataSource(), null); return new SuspendedResourcesHolder(resumed); } return null; }这意味着内层方法执行时外层事务的 Connection 被释放ThreadLocal 被清空。内层开启全新事务使用新的 Connection。当内层方法执行完毕commitTransactionAfterReturning()会提交该事务。但外层方法继续执行时若抛出异常外层事务回滚而内层已提交无法撤销。正确方案明确事务边界与异常处理我们重新设计避免在业务关键路径中使用 REQUIRES_NEW除非明确接受“部分提交”将必须独立提交的操作异步化通过消息队列保证最终一致性使用编程式事务控制更细粒度。改造后代码Service public class OrderService { Autowired private InventoryService inventoryService; Autowired private PointsService pointsService; Autowired private TransactionTemplate transactionTemplate; Autowired private AuditLogProducer auditLogProducer; Transactional public void createOrder(Order order) { inventoryService.deduct(order.getSkuId(), order.getQuantity()); // 使用编程式事务确保积分操作独立提交 transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); transactionTemplate.execute(status - { pointsService.deduct(order.getUserId(), order.getPoints()); return null; }); // 异步发送审计日志不阻塞主事务 auditLogProducer.sendAuditLog(order.getUserId(), ORDER_CREATE, order.getId()); } }同时PointsService.deduct()不再加Transactional避免双重代理问题。复盘从源码理解事务传播的本质这次走读让我们意识到Transactional不是银弹传播行为的选择必须基于业务语义REQUIRES_NEW的真正代价是“事务隔离”而非“性能开销”ThreadLocal 是 Spring 事务上下文的核心载体理解其生命周期至关重要编程式事务在复杂场景下比声明式更可控。我们也发现一个常见误区很多人以为Transactional是“数据库事务”的代理其实它是“Spring 事务同步机制”的封装。真正的数据库事务由 Connection 控制而 Spring 通过 ThreadLocal 绑定 Connection实现声明式管理。技术补丁包Spring 事务传播机制核心原理原理基于 AOP 拦截方法调用通过TransactionSynchronizationManager使用 ThreadLocal 绑定事务资源如 Connection不同传播行为决定是加入现有事务还是新建/挂起事务。 设计动机提供声明式事务管理避免手动管理 Connection 和事务边界。 边界条件REQUIRES_NEW 会挂起当前事务若外层异常回滚内层已提交无法撤销。 落地建议优先使用 REQUIRED仅在明确需要独立提交时使用 REQUIRES_NEW并配合异步补偿机制。TransactionSynchronizationManager 的 ThreadLocal 机制原理使用ThreadLocalMapObject, Object存储当前线程的事务资源dataSource - Connection、同步器列表、事务名称等。 设计动机确保同一线程内多个事务拦截器能共享事务上下文避免重复开启事务。 边界条件异步线程、线程池复用会导致 ThreadLocal 污染或丢失需手动清理或传递上下文。 落地建议在异步场景中使用TransactionContextHolder或消息头传递事务 ID避免依赖 ThreadLocal。REQUIRES_NEW 与数据库连接的绑定关系原理每次 REQUIRES_NEW 都会调用DataSourceUtils.getConnection()获取新连接执行setAutoCommit(false)开启新事务。 设计动机确保事务隔离性避免脏读、不可重复读。 边界条件频繁使用 REQUIRES_NEW 会导致连接池压力增大增加数据库负载。 落地建议结合连接池监控如 HikariCP 的 active connections限制 REQUIRES_NEW 使用频率或改用异步消息解耦。声明式事务 vs 编程式事务的选型建议原理声明式基于Transactional AOP编程式基于TransactionTemplate或TransactionManager手动控制。 设计动机声明式简洁编程式灵活适用于复杂事务边界控制。 边界条件声明式无法在同一个类内部调用时生效AOP 代理限制编程式可精确控制事务范围。 落地建议简单场景用声明式嵌套事务、条件回滚、多数据源等复杂场景优先使用编程式事务。