一、核心概念回顾
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() { // 业务操作 }行为分析:
- 事务失效:由于自调用绕过代理,
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、SQLException)
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:94119.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、原因分析
- 自调用导致代理失效(同类中方法调用)
- 方法非
public(动态代理限制) - 异常未抛出或未被 Spring 管理(如内部捕获未抛)
- 数据库引擎不支持事务(如 MyISAM)
10.1.3、解决方案
- 自调用问题:通过代理对象调用或使用
AopContext - 方法修饰符:确保方法为
public - 异常处理:确保异常被正确抛出或由 Spring 管理
- 数据库引擎:使用支持事务的引擎(如 InnoDB)
10.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 明确回滚条件 |
| 防御式编程 | 在事务边界处捕获异常并处理 |
| 性能优化 | 只读查询使用 readOnly=true,合理设置超时 |
| 分布式事务 | 使用 Seata 等框架处理跨服务事务 |
11.2、同类调用场景总结
| 调用场景 | 事务是否生效 | 关键原因与解决方案 |
|---|---|---|
| 事务A(REQUIRED) → 事务B(REQUIRED) | 是 | 同一事务,共享回滚边界 |
| 事务A(REQUIRED) → 事务B(REQUIRES_NEW) | 否 | 自调用绕过代理,需通过代理对象调用 |
| 非事务A → 事务B | 否 | 自调用绕过代理,需通过代理对象调用 |
| 事务A(REQUIRED) → 非事务B | 是 | B在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、实战建议
- 避免同类事务调用:
- 将事务方法拆分到不同类中,强制通过代理调用
- 谨慎使用 REQUIRES_NEW:
- 独立事务可能导致锁竞争或数据不一致,需评估业务场景
- 异常处理标准化:
- 在 Service 层统一抛出
RuntimeException,或在 Controller 层处理检查型异常
- 防御式编程:
- 在事务边界方法中,避免过度捕获异常导致事务状态不明确
// 正确示例:捕获后显式回滚 @Transactional public void processOrder() { try { orderService.create(); paymentService.charge(); } catch (PaymentException e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); throw new OrderFailedException("支付失败", e); } }通过深入理解事务传播、异常处理、同类/跨类调用机制及隔离机制,开发者可精准控制数据一致性边界,避免生产环境中的隐蔽问题,构建高可靠的事务化分布式系统。