news 2026/4/19 13:44:41

Spring 声明式事务完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring 声明式事务完整指南

一、核心概念回顾

1.1、声明式事务的本质

1.1.1、问题现象

需要简化事务管理代码,避免繁琐的编程式事务控制。

1.1.2、解决方案

Spring 通过AOP 动态代理对标注@Transactional的方法进行增强,在方法执行前后添加事务管理逻辑(开启、提交、回滚)。

AOP 动态代理机制图

AOP动态代理

关键组件

  • PlatformTransactionManager:事务管理器(如DataSourceTransactionManager
  • TransactionInterceptor:拦截事务方法,执行事务逻辑
  • TransactionalEventListener:事务事件监听机制

1.2、@Transactional 核心注解

关键属性

属性说明默认值
propagation事务传播行为REQUIRED
isolation事务隔离级别DEFAULT(依赖数据库)
rollbackFor指定触发回滚的异常类型RuntimeException、Error
noRollbackFor指定不回滚的异常类型
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_NEW

2.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、NESTED

2.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、SUPPORTS

@Transactional(propagation = Propagation.SUPPORTS) public List<Order> queryOrders() { // 如果当前有事务,加入事务;否则非事务运行 }

2.5.2、NOT_SUPPORTED

@Transactional(propagation = Propagation.NOT_SUPPORTED) public void writeLog(String message) { // 非事务执行 }

2.5.3、NEVER

@Transactional(propagation = Propagation.NEVER) public void auditAction() { // 非事务执行,若存在事务则抛异常 }

2.5.4、MANDATORY

@Transactional(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 → 事务方法 B(REQUIRED)

代码示例

@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 → 事务方法 B(REQUIRES_NEW)

代码示例

@Transactional public void methodA() { // 业务操作 methodB(); // 内层事务为 REQUIRES_NEW } @Transactional(propagation = Propagation.REQUIRES_NEW) public void methodB() { // 业务操作 }

行为分析

  • 事务失效:由于自调用绕过代理,methodBREQUIRES_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() { // 非事务操作 }

行为分析

  • methodBmethodA的事务上下文中执行(即加入事务
  • 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、原因分析

  • 自动回滚异常RuntimeExceptionError
  • 不回滚异常:检查型异常(如IOExceptionSQLException

6.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(List<Data> dataList) { // 超时自动回滚 }

7.2、只读事务优化

7.2.1、问题现象

查询操作可能影响数据库性能。

7.2.2、解决方案

@Transactional(readOnly = true) public List<Report> 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.enabled=true
  • 每个微服务配置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:9411

9.1.2、追踪效果

  • 事务 ID(Trace 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.interceptor=TRACE

十、常见问题与解决方案

10.1、事务未生效

10.1.1、问题现象

@Transactional注解标注的方法事务未生效。

10.1.2、原因分析

  1. 自调用导致代理失效(同类中方法调用)
  2. 方法非public(动态代理限制)
  3. 异常未抛出或未被 Spring 管理(如内部捕获未抛)
  4. 数据库引擎不支持事务(如 MyISAM)

10.1.3、解决方案

  1. 自调用问题:通过代理对象调用或使用AopContext
  2. 方法修饰符:确保方法为public
  3. 异常处理:确保异常被正确抛出或由 Spring 管理
  4. 数据库引擎:使用支持事务的引擎(如 InnoDB)

10.2、事务传播行为失效

10.2.1、问题现象

设置的传播行为未按预期工作。

10.2.2、原因分析

  1. 同类方法调用绕过代理
  2. 事务管理器配置错误

10.2.3、解决方案

  1. 将事务方法拆分到不同类中
  2. 检查事务管理器配置:
@Configuration @EnableTransactionManagement public class TransactionConfig { @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }

十一、最佳实践

11.1、场景推荐方案

场景推荐方案
事务粒度控制避免长事务,尽量在 Service 层管理事务
传播行为选择默认 REQUIRED,需要独立操作时用 REQUIRES_NEW
异常处理通过 rollbackFor 明确回滚条件
防御式编程在事务边界处捕获异常并处理
性能优化只读查询使用 readOnly=true,合理设置超时
分布式事务使用 Seata 等框架处理跨服务事务

11.2、同类调用场景总结

调用场景事务是否生效关键原因与解决方案
事务A(REQUIRED) → 事务B(REQUIRED)同一事务,共享回滚边界
事务A(REQUIRED) → 事务B(REQUIRES_NEW)自调用绕过代理,需通过代理对象调用
非事务A → 事务B自调用绕过代理,需通过代理对象调用
事务A(REQUIRED) → 非事务BB在A的事务中执行,受事务控制
事务中try-catch未处理异常提交需手动标记回滚或重新抛出RuntimeException

11.3、高频问题 QA

Q1:跨服务调用时,@Transactional 如何传递事务上下文?

  • 本地事务:通过线程绑定的事务同步管理器传递
  • 分布式事务:需依赖 Seata 等框架注入全局事务 XID

Q2:如何在 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 的 XID

11.4、实战建议

  1. 避免同类事务调用
  • 将事务方法拆分到不同类中,强制通过代理调用
  1. 谨慎使用 REQUIRES_NEW
  • 独立事务可能导致锁竞争或数据不一致,需评估业务场景
  1. 异常处理标准化
  • 在 Service 层统一抛出RuntimeException,或在 Controller 层处理检查型异常
  1. 防御式编程
  • 在事务边界方法中,避免过度捕获异常导致事务状态不明确
// 正确示例:捕获后显式回滚 @Transactional public void processOrder() { try { orderService.create(); paymentService.charge(); } catch (PaymentException e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); throw new OrderFailedException("支付失败", e); } }

通过深入理解事务传播、异常处理、同类/跨类调用机制及隔离机制,开发者可精准控制数据一致性边界,避免生产环境中的隐蔽问题,构建高可靠的事务化分布式系统。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/19 13:34:59

如何快速使用ChampR英雄联盟助手:终极出装符文配置指南

如何快速使用ChampR英雄联盟助手&#xff1a;终极出装符文配置指南 【免费下载链接】champr &#x1f436; Yet another League of Legends helper 项目地址: https://gitcode.com/gh_mirrors/ch/champr ChampR是一款专为英雄联盟玩家设计的开源辅助工具&#xff0c;它能…

作者头像 李华
网站建设 2026/4/19 13:32:38

用AirSim和Python玩转无人机视觉:三种深度图详解与点云生成实战

用AirSim和Python玩转无人机视觉&#xff1a;三种深度图详解与点云生成实战 无人机视觉感知技术的快速发展&#xff0c;让三维环境重建与自主导航成为可能。微软开源的AirSim仿真平台&#xff0c;为开发者提供了高度逼真的无人机视觉数据生成环境。本文将深入解析DepthVis、Dep…

作者头像 李华
网站建设 2026/4/19 13:31:56

告别手动保存!用Python自动化下载雪球文章并生成带书签的PDF合集

告别手动保存&#xff01;用Python自动化下载雪球文章并生成带书签的PDF合集 每次在雪球上看到有价值的投资分析文章&#xff0c;你是不是也习惯性地点击收藏&#xff1f;但收藏夹里的内容越来越多&#xff0c;查找起来反而更麻烦。更糟的是&#xff0c;有些文章可能因为各种原…

作者头像 李华