Spring 声明式事务完整指南

张开发
2026/4/19 13:44:44 15 分钟阅读

分享文章

Spring 声明式事务完整指南
一、核心概念回顾1.1、声明式事务的本质1.1.1、问题现象需要简化事务管理代码避免繁琐的编程式事务控制。1.1.2、解决方案Spring 通过AOP 动态代理对标注Transactional的方法进行增强在方法执行前后添加事务管理逻辑开启、提交、回滚。AOP 动态代理机制图AOP动态代理关键组件PlatformTransactionManager事务管理器如DataSourceTransactionManagerTransactionInterceptor拦截事务方法执行事务逻辑TransactionalEventListener事务事件监听机制1.2、Transactional 核心注解关键属性属性说明默认值propagation事务传播行为REQUIREDisolation事务隔离级别DEFAULT依赖数据库rollbackFor指定触发回滚的异常类型RuntimeException、ErrornoRollbackFor指定不回滚的异常类型无timeout事务超时时间秒-1无限制readOnly是否只读事务false二、事务传播行为详解2.1、传播行为定义传播行为描述REQUIRED默认值支持当前事务不存在则新建事务REQUIRES_NEW新建独立事务挂起当前事务若有NESTED嵌套事务依赖外层事务提交但可独立回滚需数据库支持MANDATORY必须存在事务否则抛出异常SUPPORTS当前有事务则加入否则非事务运行NOT_SUPPORTED非事务运行挂起当前事务若有NEVER非事务运行存在事务则抛出异常2.2、REQUIRED默认2.2.1、问题现象外层事务存在内层方法加入事务。2.2.2、代码示例Service public class OrderService { Autowired private PaymentService paymentService; Transactional public void createOrder(Order order) { orderRepository.save(order); paymentService.processPayment(order); // 传播行为REQUIRED } } Service public class PaymentService { Transactional(propagation Propagation.REQUIRED) public void processPayment(Order order) { // 扣款逻辑 } }2.2.3、行为分析内外层共享同一事务任意环节抛出RuntimeException整个事务回滚异常处理若外层捕获内层异常事务仍可能提交需显式处理REQUIRED 传播行为图2.3、REQUIRES_NEW2.3.1、问题现象内层事务独立执行与外层事务隔离。2.3.2、代码示例Transactional public void createOrder(Order order) { orderRepository.save(order); try { paymentService.processPayment(order); // REQUIRES_NEW } catch (PaymentException e) { // 捕获异常外层事务继续提交 } } // PaymentService Transactional(propagation Propagation.REQUIRES_NEW) public void processPayment(Order order) { // 扣款逻辑独立事务 }2.3.3、行为分析事务独立内层事务提交或回滚不影响外层事务典型用例支付与订单分离支付失败仍需保留订单REQUIRES_NEW 传播行为图2.4、NESTED2.4.1、问题现象内层事务作为外层事务的子事务依赖数据库保存点。2.4.2、代码示例Transactional public void batchProcess() { insertMasterRecord(); try { detailService.insertDetails(); // NESTED } catch (DataException e) { // 仅回滚子事务外层事务继续 } updateStatus(); } // DetailService Transactional(propagation Propagation.NESTED) public void insertDetails() { // 插入明细记录嵌套事务 }2.4.3、行为分析子事务回滚回滚到保存点不影响外层事务外层回滚所有子事务一同回滚支持数据库MySQL 的 InnoDB 引擎支持保存点机制NESTED 嵌套事务保存点图2.5、其他传播行为2.5.1、SUPPORTSTransactional(propagation Propagation.SUPPORTS) public ListOrder queryOrders() { // 如果当前有事务加入事务否则非事务运行 }2.5.2、NOT_SUPPORTEDTransactional(propagation Propagation.NOT_SUPPORTED) public void writeLog(String message) { // 非事务执行 }2.5.3、NEVERTransactional(propagation Propagation.NEVER) public void auditAction() { // 非事务执行若存在事务则抛异常 }2.5.4、MANDATORYTransactional(propagation Propagation.MANDATORY) public void validatePayment(Payment payment) { // 必须在事务中调用否则抛出 IllegalTransactionStateException }三、事务隔离级别3.1、隔离级别配置Transactional(isolation Isolation.REPEATABLE_READ) public void updateStock(Long productId) { // 可重复读隔离级别 }3.2、隔离级别说明隔离级别脏读不可重复读幻读READ_UNCOMMITTED✓✓✓READ_COMMITTED✗✓✓REPEATABLE_READ✗✗✓SERIALIZABLE✗✗✗3.3、典型问题与解决方案脏读使用READ_COMMITTED不可重复读使用REPEATABLE_READ幻读使用SERIALIZABLE或行锁SELECT ... FOR UPDATE四、同类方法调用事务场景4.1、自调用问题本质4.1.1、问题现象同一类中非事务方法调用事务方法时事务失效。Service public class OrderService { public void createOrder(Order order) { saveOrder(order); // 自调用事务失效 processPayment(order); } Transactional public void processPayment(Order order) { // 扣款逻辑 } }4.1.2、原因分析Spring 事务基于AOP 动态代理实现自调用绕过代理对象直接调用原始方法导致事务失效。4.1.3、动态代理机制问题本质Spring 使用 JDK 动态代理或 CGLIB 创建代理对象Transactional注解的方法由代理对象拦截添加事务逻辑同类方法调用时使用的是this引用而非代理对象4.2、同类调用典型场景4.2.1、事务方法 A → 事务方法 BREQUIRED代码示例Service public class OrderService { Transactional public void methodA() { // 业务操作 methodB(); // 调用同类事务方法 } Transactional // 默认 Propagation.REQUIRED public void methodB() { // 业务操作 } }行为分析实际执行的是同一个事务因为methodB的传播行为为REQUIRED会加入methodA的事务若methodB抛出RuntimeException整个事务回滚事务流程图陷阱警示 若methodA捕获methodB的异常且未重新抛出事务将提交Transactional public void methodA() { try { methodB(); } catch (Exception e) { // 捕获异常但未处理 ➔ 事务提交 } }4.2.2、事务方法 A → 事务方法 BREQUIRES_NEW代码示例Transactional public void methodA() { // 业务操作 methodB(); // 内层事务为 REQUIRES_NEW } Transactional(propagation Propagation.REQUIRES_NEW) public void methodB() { // 业务操作 }行为分析事务失效由于自调用绕过代理methodB的REQUIRES_NEW不会生效实际执行的是同一个事务而非独立事务4.2.3、非事务方法 A → 事务方法 B代码示例public void methodA() { // 非事务操作 methodB(); // 调用事务方法 } Transactional public void methodB() { // 业务操作 }行为分析事务失效因为methodA未通过代理调用直接调用methodB导致事务未开启methodB中的数据库操作将自动提交依赖数据库的自动提交设置4.2.4、事务方法 A → 非事务方法 B代码示例Transactional public void methodA() { // 事务操作 methodB(); // 调用非事务方法 } public void methodB() { // 非事务操作 }行为分析methodB在methodA的事务上下文中执行即加入事务若methodB抛出异常整个事务回滚4.3、自调用问题解决方案4.3.1、方案一注入自身代理对象Service public class OrderService { Autowired private ApplicationContext context; private OrderService selfProxy; PostConstruct public void init() { selfProxy context.getBean(OrderService.class); } public void createOrder(Order order) { saveOrder(order); // 通过代理对象调用 selfProxy.processPayment(order); } Transactional public void processPayment(Order order) { // 扣款逻辑 } }4.3.2、方案二使用 AopContext 获取代理启动类开启配置EnableAspectJAutoProxy(exposeProxy true) SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }代码示例Service public class OrderService { public void createOrder(Order order) { saveOrder(order); // 获取当前代理对象 ((OrderService) AopContext.currentProxy()).processPayment(order); } Transactional public void processPayment(Order order) { // 扣款逻辑 } }4.3.3、方案三代码重构将事务方法拆分到另一个类中强制通过 Spring 代理调用。Service public class OrderService { Autowired private PaymentService paymentService; public void createOrder(Order order) { saveOrder(order); // 通过另一个类的代理对象调用 paymentService.processPayment(order); } } Service public class PaymentService { Transactional public void processPayment(Order order) { // 扣款逻辑 } }五、跨类方法调用事务场景5.1、跨类调用核心机制5.1.1、事务传播行为的关键作用跨类方法调用的事务行为由传播行为Propagation和异常触发回滚规则共同决定。Spring 通过代理机制将事务切面织入目标方法不同类之间的调用链路形成事务链。5.1.2、事务上下文传递原理事务同步管理器TransactionSynchronizationManager绑定事务上下文到当前线程传播行为决定是否继承当前事务、挂起事务或新建事务5.2、跨类调用典型场景5.2.1、REQUIRED 传播场景问题现象主事务依赖外部服务支付操作需与订单保存处于同一事务。代码示例// OrderService Service public class OrderService { Autowired private PaymentService paymentService; Transactional public void placeOrder(Order order) { orderRepository.save(order); // 主事务操作 paymentService.processPayment(order); // 调用外部服务REQUIRED 传播 } } // PaymentService Service public class PaymentService { Transactional(propagation Propagation.REQUIRED) public void processPayment(Order order) { // 扣款逻辑与主事务合并 } }行为分析processPayment()加入主事务任一操作失败均整体回滚事务链placeOrder()→processPayment()共用同一事务跨类调用事务流程图5.2.2、REQUIRES_NEW 传播场景问题现象订单提交后需记录审计日志即使订单失败日志仍需独立保存。代码示例// OrderService Transactional public void placeOrder(Order order) { try { orderRepository.save(order); paymentService.charge(order); // REQUIRES_NEW } catch (PaymentException e) { auditService.logFailure(order, e); // REQUIRES_NEW throw e; } } // AuditService Service public class AuditService { Transactional(propagation Propagation.REQUIRES_NEW) public void logFailure(Order order, Exception e) { // 独立事务记录失败日志 } }行为分析charge()和logFailure()均为独立事务失败不影响主事务回滚范围异常陷阱若logFailure()抛出异常主事务仍会回滚需额外处理5.2.3、NESTED 传播场景问题现象更新订单状态时需同时更新库存。若库存更新失败仅回滚库存操作订单状态仍保留。代码示例// OrderService Transactional public void updateOrderStatus(Long orderId, String status) { Order order orderRepository.findById(orderId).orElseThrow(); order.setStatus(status); orderRepository.save(order); try { inventoryService.adjustStock(order); // NESTED 传播 } catch (InventoryException e) { // 仅回滚库存调整订单状态更新保留 } } // InventoryService Service public class InventoryService { Transactional(propagation Propagation.NESTED) public void adjustStock(Order order) { // 嵌套事务依赖数据库保存点 } }关键点需数据库支持保存点如 MySQL 的 InnoDB若主事务在后续操作中失败所有嵌套事务一并回滚5.2.4、MANDATORY 传播场景问题现象支付校验必须在事务上下文中执行防止非事务调用导致数据不一致。代码示例// PaymentService Service public class PaymentService { Transactional(propagation Propagation.MANDATORY) public void validatePayment(Payment payment) { // 必须在事务中调用 } } // 错误用法非事务调用 public void processPaymentWithoutTx(Payment payment) { paymentService.validatePayment(payment); // 抛出 IllegalTransactionStateException }适用场景分层架构中强制业务层方法由事务性入口调用5.3、跨类调用事务陷阱5.3.1、异常未穿透代理导致不回滚问题现象支付服务抛出非受检异常但主事务未回滚。原因分析// PaymentService Transactional public void charge(Order order) { try { paymentGateway.charge(order); } catch (PaymentException e) { // 捕获异常未重新抛出 → 事务提交 } }解决方案抛出受事务管理的异常默认回滚 RuntimeException 和 Error手动标记回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();5.3.2、跨服务循环依赖导致事务死锁问题场景// OrderService Transactional public void placeOrder(Order order) { paymentService.charge(order); } // PaymentService Transactional public void charge(Order order) { orderService.updateOrderStatus(order.getId(), PAID); // 循环调用 }死锁原因事务方法间相互调用持有不同数据库连接锁解决方案重构代码拆分事务边界使用异步消息解耦如 RabbitMQ六、异常处理与事务回滚6.1、默认回滚规则6.1.1、问题现象事务未按预期回滚。6.1.2、原因分析自动回滚异常RuntimeException和Error不回滚异常检查型异常如IOException、SQLException6.2、显式配置回滚6.2.1、解决方案// 指定 SQLException 触发回滚 Transactional(rollbackFor SQLException.class) public void updateData() throws SQLException { // 数据库操作 }6.3、异常被吞噬陷阱6.3.1、问题现象Transactional public void process() { try { paymentService.charge(); } catch (PaymentException e) { // 捕获异常但未重新抛出 ➔ 事务提交 } }6.3.2、解决方案若需回滚需手动标记回滚catch (PaymentException e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); }异常处理与回滚决策流程图七、事务超时与只读优化7.1、超时设置7.1.1、问题现象长时间运行的事务占用数据库连接资源。7.1.2、解决方案Transactional(timeout 5) // 单位秒 public void batchInsert(ListData dataList) { // 超时自动回滚 }7.2、只读事务优化7.2.1、问题现象查询操作可能影响数据库性能。7.2.2、解决方案Transactional(readOnly true) public ListReport generateReport() { // 只读事务可能启用数据库优化 }八、分布式事务8.1、基于 Seata 的 AT 模式8.1.1、问题现象订单服务、库存服务、账户服务分属不同微服务需保证数据最终一致性。8.1.2、代码示例1. 全局事务入口GlobalTransactional public void placeOrderDistributed(Order order) { orderService.create(order); inventoryService.deduct(order.getProductId(), order.getQuantity()); accountService.debit(order.getUserId(), order.getAmount()); }2. 分支事务配置// InventoryService Transactional GlobalLock public void deduct(String productId, int quantity) { // 库存扣减 }8.1.3、关键配置seata.enabledtrue每个微服务配置undo_log表及 Seata Server 地址Seata AT 模式分布式事务架构图Seata AT 模式工作流程九、事务调试与监控9.1、可视化事务链路追踪9.1.1、使用 Spring Cloud Sleuth Zipkin# application.yml spring: sleuth: enabled: true zipkin: base-url: HTTP://localhost:94119.1.2、追踪效果事务 IDTrace ID贯穿跨服务调用可视化事务链路9.2、动态调整事务超时Transactional(timeout 30) public void batchProcess() { // 根据数据量动态计算超时 int dynamicTimeout calculateTimeout(); TransactionTemplate transactionTemplate new TransactionTemplate(transactionManager); transactionTemplate.setTimeout(dynamicTimeout); transactionTemplate.execute(status - { // 业务逻辑 return null; }); }9.3、开启事务日志logging.level.org.springframework.transaction.interceptorTRACE十、常见问题与解决方案10.1、事务未生效10.1.1、问题现象Transactional注解标注的方法事务未生效。10.1.2、原因分析自调用导致代理失效同类中方法调用方法非public动态代理限制异常未抛出或未被 Spring 管理如内部捕获未抛数据库引擎不支持事务如 MyISAM10.1.3、解决方案自调用问题通过代理对象调用或使用AopContext方法修饰符确保方法为public异常处理确保异常被正确抛出或由 Spring 管理数据库引擎使用支持事务的引擎如 InnoDB10.2、事务传播行为失效10.2.1、问题现象设置的传播行为未按预期工作。10.2.2、原因分析同类方法调用绕过代理事务管理器配置错误10.2.3、解决方案将事务方法拆分到不同类中检查事务管理器配置Configuration EnableTransactionManagement public class TransactionConfig { Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }十一、最佳实践11.1、场景推荐方案场景推荐方案事务粒度控制避免长事务尽量在 Service 层管理事务传播行为选择默认 REQUIRED需要独立操作时用 REQUIRES_NEW异常处理通过 rollbackFor 明确回滚条件防御式编程在事务边界处捕获异常并处理性能优化只读查询使用 readOnlytrue合理设置超时分布式事务使用 Seata 等框架处理跨服务事务11.2、同类调用场景总结调用场景事务是否生效关键原因与解决方案事务AREQUIRED → 事务BREQUIRED是同一事务共享回滚边界事务AREQUIRED → 事务BREQUIRES_NEW否自调用绕过代理需通过代理对象调用非事务A → 事务B否自调用绕过代理需通过代理对象调用事务AREQUIRED → 非事务B是B在A的事务中执行受事务控制事务中try-catch未处理异常提交需手动标记回滚或重新抛出RuntimeException11.3、高频问题 QAQ1跨服务调用时Transactional 如何传递事务上下文本地事务通过线程绑定的事务同步管理器传递分布式事务需依赖 Seata 等框架注入全局事务 XIDQ2如何在 Feign 客户端调用中保持事务FeignClient(name inventory-service) public interface InventoryClient { PostMapping(/inventory/deduct) void deductStock(RequestBody DeductRequest request); } // 使用 Seata 代理 Feign 调用 GlobalTransactional public void placeOrderWithFeign(Order order) { orderService.save(order); inventoryClient.deductStock(new DeductRequest(order)); }要点确保 Feign 拦截器传递 Seata 的 XID11.4、实战建议避免同类事务调用将事务方法拆分到不同类中强制通过代理调用谨慎使用 REQUIRES_NEW独立事务可能导致锁竞争或数据不一致需评估业务场景异常处理标准化在 Service 层统一抛出RuntimeException或在 Controller 层处理检查型异常防御式编程在事务边界方法中避免过度捕获异常导致事务状态不明确// 正确示例捕获后显式回滚 Transactional public void processOrder() { try { orderService.create(); paymentService.charge(); } catch (PaymentException e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); throw new OrderFailedException(支付失败, e); } }通过深入理解事务传播、异常处理、同类/跨类调用机制及隔离机制开发者可精准控制数据一致性边界避免生产环境中的隐蔽问题构建高可靠的事务化分布式系统。

更多文章