首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Spring 事务传播机制

Spring 事务传播机制

作者头像
果酱带你啃java
发布2026-04-14 10:09:38
发布2026-04-14 10:09:38
590
举报

引言:事务传播的 "隐形之手"

在分布式系统和复杂业务场景中,事务管理是保证数据一致性的核心环节。想象这样一个场景:用户下单时,系统需要扣减库存、创建订单、扣减余额,这三个操作必须同时成功或同时失败。但如果这三个操作分别位于不同的服务或方法中,如何确保它们处于同一个事务中?

这就是 Spring 事务传播机制要解决的问题。它像一只 "隐形之手",在方法调用之间协调事务的创建、加入和结束,确保数据操作的一致性。理解 Spring 事务传播机制,不仅能帮助你避免常见的事务问题,还能让你在复杂业务场景中设计出更健壮的系统。

本文将从基础概念到实战案例,全面解析 Spring 的 7 种事务传播行为,带你彻底掌握这一核心技术。

一、事务与事务传播的基础概念

1.1 什么是事务?

事务(Transaction)是数据库操作的基本单元,它是一系列操作的集合,这些操作要么全部成功,要么全部失败,不存在部分成功的情况。

事务具有 ACID 特性:

  • 原子性(Atomicity):事务中的所有操作要么全部完成,要么全部不完成
  • 一致性(Consistency):事务执行前后,数据库的状态保持一致
  • 隔离性(Isolation):多个事务并发执行时,彼此不会相互干扰
  • 持久性(Durability):事务一旦提交,其结果就是永久性的

1.2 什么是事务传播机制?

当一个事务方法调用另一个事务方法时,Spring 需要决定如何处理这两个方法的事务关系:是使用同一个事务,还是创建新的事务?事务传播机制(Transaction Propagation)就是定义这种处理规则的机制。

事务传播机制解决的核心问题是:当多个事务方法嵌套调用时,如何管理事务的边界和行为

1.3 Spring 中事务传播机制的定义

Spring 在Propagation枚举类中定义了 7 种事务传播行为:

代码语言:javascript
复制
public enum Propagation {
    REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
    SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
    MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
    REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
    NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
    NEVER(TransactionDefinition.PROPAGATION_NEVER),
    NESTED(TransactionDefinition.PROPAGATION_NESTED);

    private final int value;

    Propagation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }
}
代码语言:javascript
复制

这些传播行为定义了内部方法如何与外部方法的事务进行交互,是 Spring 事务管理的核心。

二、Spring 事务传播机制的实现原理

要理解事务传播机制,首先需要了解 Spring 是如何管理事务的。

2.1 Spring 事务管理的核心组件

  • PlatformTransactionManager:事务管理的核心接口,定义了事务的创建、提交和回滚方法
  • TransactionDefinition:定义事务的属性,包括传播行为、隔离级别、超时时间等
  • TransactionStatus:代表当前事务的状态,提供了事务的状态信息和操作方法

2.2 事务传播的工作流程

当一个标注了@Transactional的方法被调用时,Spring 会通过 AOP 拦截该方法,执行以下步骤:

事务传播机制的核心逻辑就在步骤 D 中,Spring 会根据当前是否存在事务以及指定的传播行为,决定如何处理事务。

2.3 事务的绑定与传播

Spring 通过ThreadLocal将事务与当前线程绑定,实现事务的传播:

代码语言:javascript
复制
// 简化版的事务绑定逻辑
public class TransactionSynchronizationManager {
    // 存储当前线程的事务连接
    private static final ThreadLocal<Map<Object, Object>> resources =
        new NamedThreadLocal<>("Transactional resources");

    // 存储当前线程的事务状态
    private static final ThreadLocal<TransactionStatus> transactionStatus =
        new NamedThreadLocal<>("Current transaction status");

    // 绑定资源到当前线程
    public static void bindResource(Object key, Object value) {
        Map<Object, Object> map = resources.get();
        if (map == null) {
            map = new HashMap<>();
            resources.set(map);
        }
        map.put(key, value);
    }

    // 从当前线程获取资源
    public static Object getResource(Object key) {
        Map<Object, Object> map = resources.get();
        return (map != null ? map.get(key) : null);
    }

    // 从当前线程解绑资源
    public static Object unbindResource(Object key) {
        Map<Object, Object> map = resources.get();
        if (map == null) {
            return null;
        }
        return map.remove(key);
    }
}
代码语言:javascript
复制

当方法调用时,Spring 会检查当前线程是否已绑定事务资源:

  • 如果有,说明当前已有事务在运行
  • 如果没有,说明需要创建新事务

这种线程绑定机制是事务传播的基础,使得嵌套调用的方法能够感知到外部方法的事务。

三、7 种事务传播行为详解

Spring 定义了 7 种事务传播行为,每种行为都有其特定的使用场景和行为规则。

3.1 REQUIRED:默认的传播行为

行为定义:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

适用场景:大多数业务场景,这是 Spring 的默认传播行为。

示例代码

代码语言:javascript
复制
/**
 * 订单服务
 * @author ken
 */
@Service
@Slf4j
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private InventoryService inventoryService;

    /**
     * 创建订单(无事务)
     */
    public void createOrderWithoutTx(Order order) {
        log.info("创建订单: {}", order);
        orderMapper.insert(order);
        // 调用扣减库存方法(REQUIRED)
        inventoryService.reduceStock(order.getProductId(), order.getQuantity());
    }

    /**
     * 创建订单(有事务)
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrderWithTx(Order order) {
        log.info("创建订单: {}", order);
        orderMapper.insert(order);
        // 调用扣减库存方法(REQUIRED)
        inventoryService.reduceStock(order.getProductId(), order.getQuantity());
    }
}

/**
 * 库存服务
 * @author ken
 */
@Service
@Slf4j
public class InventoryService {
    @Autowired
    private InventoryMapper inventoryMapper;

    /**
     * 扣减库存
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void reduceStock(Long productId, Integer quantity) {
        log.info("扣减库存, 商品ID: {}, 数量: {}", productId, quantity);
        Inventory inventory = inventoryMapper.selectById(productId);
        if (inventory.getStock() < quantity) {
            throw new InsufficientStockException("库存不足");
        }
        inventory.setStock(inventory.getStock() - quantity);
        inventoryMapper.updateById(inventory);
    }
}
代码语言:javascript
复制

执行结果分析

  1. 调用createOrderWithoutTx()
    • 外部方法没有事务
    • 内部方法reduceStock()会创建新事务
    • 如果内部方法抛出异常,只有库存操作会回滚,订单会保留(数据不一致)
  2. 调用createOrderWithTx()
    • 外部方法创建新事务
    • 内部方法reduceStock()加入外部事务
    • 如果内部方法抛出异常,整个事务会回滚,订单和库存操作都会撤销(数据一致)

3.2 SUPPORTS:支持当前事务

行为定义:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式运行。

适用场景:适用于那些 "可选事务" 的方法,这些方法在有事务时就参与事务,没有事务时也能正常运行。例如查询操作,通常不需要事务,但如果已经在事务中执行,也可以参与事务。

示例代码

代码语言:javascript
复制
/**
 * 统计服务
 * @author ken
 */
@Service
@Slf4j
public class StatisticsService {
    @Autowired
    private StatisticsMapper statisticsMapper;

    /**
     * 更新统计数据
     * 支持事务,但不强制要求
     */
    @Transactional(propagation = Propagation.SUPPORTS)
    public void updateStatistics(Long productId) {
        log.info("更新商品统计数据, 商品ID: {}", productId);
        Statistics stats = statisticsMapper.selectById(productId);
        if (stats == null) {
            stats = new Statistics();
            stats.setProductId(productId);
            stats.setSaleCount(1);
            statisticsMapper.insert(stats);
        } else {
            stats.setSaleCount(stats.getSaleCount() + 1);
            statisticsMapper.updateById(stats);
        }
    }
}

// 在OrderService中添加调用
@Service
@Slf4j
public class OrderService {
    // ... 其他代码省略 ...

    /**
     * 创建订单(有事务)
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrderWithTx(Order order) {
        log.info("创建订单: {}", order);
        orderMapper.insert(order);
        inventoryService.reduceStock(order.getProductId(), order.getQuantity());
        // 调用统计服务(SUPPORTS)
        statisticsService.updateStatistics(order.getProductId());
    }

    /**
     * 查询订单(无事务)
     */
    public Order queryOrder(Long orderId) {
        Order order = orderMapper.selectById(orderId);
        // 调用统计服务(SUPPORTS)
        statisticsService.updateStatistics(order.getProductId());
        return order;
    }
}
代码语言:javascript
复制

执行结果分析

  1. createOrderWithTx()中调用updateStatistics()
    • 外部存在事务,统计方法加入该事务
    • 如果订单创建失败回滚,统计数据也会回滚
  2. queryOrder()中调用updateStatistics()
    • 外部没有事务,统计方法以非事务方式运行
    • 统计数据会直接提交,不受其他操作影响

3.3 MANDATORY:必须在事务中运行

行为定义:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

适用场景:适用于那些必须在事务中执行的操作,确保这些操作不会在无事务的环境中执行。例如关键的金融操作,必须在事务保护下执行。

示例代码

代码语言:javascript
复制
/**
 * 支付服务
 * @author ken
 */
@Service
@Slf4j
public class PaymentService {
    @Autowired
    private PaymentMapper paymentMapper;

    /**
     * 记录支付信息
     * 必须在事务中运行
     */
    @Transactional(propagation = Propagation.MANDATORY)
    public void recordPayment(Payment payment) {
        log.info("记录支付信息: {}", payment);
        paymentMapper.insert(payment);
    }
}

// 在OrderService中添加调用
@Service
@Slf4j
public class OrderService {
    // ... 其他代码省略 ...

    /**
     * 创建订单并支付(有事务)
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrderAndPay(Order order, Payment payment) {
        log.info("创建订单并支付");
        orderMapper.insert(order);
        inventoryService.reduceStock(order.getProductId(), order.getQuantity());
        // 调用支付服务(MANDATORY)
        paymentService.recordPayment(payment);
    }

    /**
     * 创建订单并支付(无事务)
     */
    public void createOrderAndPayWithoutTx(Order order, Payment payment) {
        log.info("创建订单并支付(无事务)");
        orderMapper.insert(order);
        // 调用支付服务(MANDATORY)
        paymentService.recordPayment(payment);
    }
}
代码语言:javascript
复制

执行结果分析

  1. 调用createOrderAndPay()
    • 外部存在事务,支付方法加入该事务
    • 所有操作在同一个事务中,保证数据一致性
  2. 调用createOrderAndPayWithoutTx()
    • 外部没有事务,支付方法会抛出IllegalTransactionStateException
    • 订单会被插入,但支付记录不会被插入,且会抛出异常

3.4 REQUIRES_NEW:创建新事务

行为定义:无论当前是否存在事务,都创建一个新的事务。如果当前存在事务,则将当前事务挂起。

适用场景:适用于那些需要独立事务的操作,这些操作无论外部事务成功与否都应该被提交。例如日志记录、操作审计等,即使主业务失败,这些记录也需要保留。

示例代码

代码语言:javascript
复制
/**
 * 日志服务
 * @author ken
 */
@Service
@Slf4j
public class LogService {
    @Autowired
    private OperationLogMapper logMapper;

    /**
     * 记录操作日志
     * 使用独立事务
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void recordLog(OperationLog log) {
        log.info("记录操作日志: {}", log);
        logMapper.insert(log);
    }
}

// 在OrderService中添加调用
@Service
@Slf4j
public class OrderService {
    // ... 其他代码省略 ...

    /**
     * 创建订单(有事务)
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrderWithLog(Order order) {
        log.info("创建订单: {}", order);
        // 记录操作日志(REQUIRES_NEW)
        OperationLog log = new OperationLog();
        log.setOperation("CREATE_ORDER");
        log.setContent(JSON.toJSONString(order));
        logService.recordLog(log);

        orderMapper.insert(order);
        inventoryService.reduceStock(order.getProductId(), order.getQuantity());

        // 模拟业务异常
        if (order.getAmount() < 0) {
            throw new IllegalArgumentException("订单金额不能为负数");
        }
    }
}
代码语言:javascript
复制

执行结果分析

  1. 当订单金额正常时:
    • 外部事务正常提交
    • 日志服务创建新事务并提交
    • 订单、库存和日志都成功保存
  2. 当订单金额为负数(抛出异常)时:
    • 外部事务回滚,订单和库存操作被撤销
    • 日志服务的新事务不受影响,仍然成功提交
    • 最终结果:日志被保存,订单和库存操作被回滚

3.5 NOT_SUPPORTED:不支持事务

行为定义:以非事务方式运行。如果当前存在事务,则将当前事务挂起。

适用场景:适用于那些不需要事务的操作,即使外部存在事务,也以非事务方式运行。例如一些耗时的查询操作,不需要事务支持,可以提高性能。

示例代码

代码语言:javascript
复制
/**
 * 报表服务
 * @author ken
 */
@Service
@Slf4j
public class ReportService {
    @Autowired
    private ReportMapper reportMapper;

    /**
     * 生成销售报表
     * 不需要事务支持
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public Report generateSalesReport(LocalDate startDate, LocalDate endDate) {
        log.info("生成销售报表, 开始日期: {}, 结束日期: {}", startDate, endDate);
        // 模拟耗时的报表生成过程
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("报表生成被中断");
        }

        Report report = new Report();
        report.setStartDate(startDate);
        report.setEndDate(endDate);
        report.setContent("销售报表内容...");
        reportMapper.insert(report);
        return report;
    }
}

// 在OrderService中添加调用
@Service
@Slf4j
public class OrderService {
    // ... 其他代码省略 ...

    /**
     * 创建订单并生成报表
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrderAndGenerateReport(Order order) {
        log.info("创建订单并生成报表");
        orderMapper.insert(order);
        inventoryService.reduceStock(order.getProductId(), order.getQuantity());

        // 调用报表服务(NOT_SUPPORTED)
        reportService.generateSalesReport(LocalDate.now().minusDays(7), LocalDate.now());

        // 模拟异常
        if (order.getQuantity() > 100) {
            throw new IllegalArgumentException("订单数量过大");
        }
    }
}
代码语言:javascript
复制

执行结果分析

  1. 当订单数量正常时:
    • 外部事务正常提交,订单和库存操作生效
    • 报表服务以非事务方式运行,报表记录被保存
  2. 当订单数量过大(抛出异常)时:
    • 外部事务回滚,订单和库存操作被撤销
    • 报表服务以非事务方式运行,不受外部事务影响,报表记录仍然被保存

3.6 NEVER:不允许事务

行为定义:以非事务方式运行。如果当前存在事务,则抛出异常。

适用场景:适用于那些绝对不能在事务中运行的操作。例如某些特定的统计或日志操作,如果在事务中运行可能会导致性能问题或数据不一致。

示例代码

代码语言:javascript
复制
/**
 * 审计服务
 * @author ken
 */
@Service
@Slf4j
public class AuditService {
    @Autowired
    private AuditLogMapper auditLogMapper;

    /**
     * 记录审计日志
     * 不允许在事务中运行
     */
    @Transactional(propagation = Propagation.NEVER)
    public void recordAuditLog(AuditLog log) {
        log.info("记录审计日志: {}", log);
        auditLogMapper.insert(log);
    }
}

// 在OrderService中添加调用
@Service
@Slf4j
public class OrderService {
    // ... 其他代码省略 ...

    /**
     * 创建订单并记录审计日志(有事务)
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrderWithAudit(Order order) {
        log.info("创建订单并记录审计日志");
        orderMapper.insert(order);

        // 调用审计服务(NEVER)
        AuditLog log = new AuditLog();
        log.setOperation("CREATE_ORDER");
        log.setOperator("system");
        auditService.recordAuditLog(log);
    }

    /**
     * 创建订单并记录审计日志(无事务)
     */
    public void createOrderWithAuditWithoutTx(Order order) {
        log.info("创建订单并记录审计日志(无事务)");
        orderMapper.insert(order);

        // 调用审计服务(NEVER)
        AuditLog log = new AuditLog();
        log.setOperation("CREATE_ORDER");
        log.setOperator("system");
        auditService.recordAuditLog(log);
    }
}
代码语言:javascript
复制

执行结果分析

  1. 调用createOrderWithAudit()
    • 外部存在事务
    • 调用审计服务时会抛出IllegalTransactionStateException
    • 外部事务回滚,订单操作被撤销
  2. 调用createOrderWithAuditWithoutTx()
    • 外部没有事务
    • 审计服务正常执行,审计日志被记录
    • 订单操作以非事务方式执行,直接提交

3.7 NESTED:嵌套事务

行为定义:如果当前存在事务,则创建一个嵌套事务(子事务),嵌套在当前事务中;如果当前没有事务,则创建一个新的事务。

嵌套事务与外部事务的关系:

  • 子事务依赖于外部事务,只有外部事务提交后,子事务才能被提交
  • 子事务可以独立回滚,回滚后不影响外部事务的继续执行
  • 如果外部事务回滚,子事务也会被回滚

适用场景:适用于那些需要部分回滚能力的场景。例如复杂的订单处理,其中某些步骤失败可以回滚该步骤,但不影响其他步骤的继续执行。

示例代码

代码语言:javascript
复制
/**
 * 积分服务
 * @author ken
 */
@Service
@Slf4j
public class PointsService {
    @Autowired
    private PointsMapper pointsMapper;

    /**
     * 增加用户积分
     * 使用嵌套事务
     */
    @Transactional(propagation = Propagation.NESTED)
    public void addPoints(Long userId, Integer points) {
        log.info("增加用户积分, 用户ID: {}, 积分: {}", userId, points);
        PointsAccount account = pointsMapper.selectById(userId);
        if (account == null) {
            account = new PointsAccount();
            account.setUserId(userId);
            account.setPoints(points);
            pointsMapper.insert(account);
        } else {
            account.setPoints(account.getPoints() + points);
            pointsMapper.updateById(account);
        }

        // 模拟积分操作失败
        if (points < 0) {
            throw new IllegalArgumentException("积分不能为负数");
        }
    }
}

// 在OrderService中添加调用
@Service
@Slf4j
public class OrderService {
    // ... 其他代码省略 ...

    /**
     * 创建订单并增加积分
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrderAndAddPoints(Order order) {
        log.info("创建订单并增加积分");
        orderMapper.insert(order);
        inventoryService.reduceStock(order.getProductId(), order.getQuantity());

        try {
            // 调用积分服务(NESTED)
            pointsService.addPoints(order.getUserId(), order.getAmount().intValue() / 10);
        } catch (IllegalArgumentException e) {
            log.error("积分操作失败, 继续执行订单流程: {}", e.getMessage());
            // 捕获子事务异常, 外部事务继续执行
        }

        // 模拟外部事务是否回滚
        if (order.getAmount() > 10000) {
            throw new IllegalArgumentException("订单金额过大, 需要人工审核");
        }
    }
}
代码语言:javascript
复制

执行结果分析

  1. 当订单金额正常且积分不为负时:
    • 外部事务和嵌套事务都正常提交
    • 订单、库存和积分操作都生效
  2. 当积分操作失败(积分 < 0)但订单金额正常时:
    • 积分服务的嵌套事务回滚,积分操作被撤销
    • 外部事务继续执行并提交,订单和库存操作生效
    • 最终结果:订单和库存操作成功,积分操作失败
  3. 当订单金额过大(抛出异常)时:
    • 无论积分操作是否成功,外部事务回滚
    • 订单、库存和积分操作都被撤销

NESTED 与 REQUIRES_NEW 的区别

  • NESTED 是嵌套在外部事务中的子事务,依赖于外部事务
  • REQUIRES_NEW 是完全独立的新事务,与外部事务没有依赖关系
  • 外部事务回滚时,NESTED 子事务会被回滚,而 REQUIRES_NEW 事务不受影响

四、事务传播机制的实战案例

4.1 订单创建全流程事务管理

假设一个完整的订单创建流程包括以下步骤:

  1. 创建订单记录
  2. 扣减商品库存
  3. 扣减用户余额
  4. 增加用户积分
  5. 记录操作日志

我们需要设计合理的事务传播机制,确保数据一致性的同时,满足业务需求。

代码语言:javascript
复制
/**
 * 订单流程服务
 * 协调订单创建的完整流程
 * @author ken
 */
@Service
@Slf4j
public class OrderProcessService {
    @Autowired
    private OrderService orderService;

    @Autowired
    private InventoryService inventoryService;

    @Autowired
    private AccountService accountService;

    @Autowired
    private PointsService pointsService;

    @Autowired
    private LogService logService;

    /**
     * 完整的订单创建流程
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public Order createCompleteOrder(OrderCreateDTO dto) {
        log.info("开始处理订单创建流程: {}", dto);

        // 1. 创建订单记录 - 参与主事务
        Order order = new Order();
        order.setUserId(dto.getUserId());
        order.setProductId(dto.getProductId());
        order.setQuantity(dto.getQuantity());
        order.setAmount(dto.getAmount());
        order.setStatus(OrderStatus.PENDING);
        orderService.createOrder(order);

        try {
            // 2. 扣减商品库存 - 参与主事务
            inventoryService.reduceStock(dto.getProductId(), dto.getQuantity());

            // 3. 扣减用户余额 - 参与主事务
            accountService.reduceBalance(dto.getUserId(), dto.getAmount());

            // 4. 增加用户积分 - 嵌套事务,失败不影响主流程
            pointsService.addPoints(dto.getUserId(), dto.getAmount().intValue() / 10);
        } catch (InsufficientStockException | InsufficientBalanceException e) {
            log.error("订单创建失败: {}", e.getMessage());
            // 库存或余额不足,回滚整个事务
            throw e;
        } catch (Exception e) {
            log.error("非关键步骤失败: {}", e.getMessage());
            // 非关键步骤失败,记录日志,继续执行
        }

        // 5. 记录操作日志 - 独立事务,必须成功
        OperationLog log = new OperationLog();
        log.setOperation("CREATE_ORDER");
        log.setContent(JSON.toJSONString(order));
        logService.recordLog(log);

        // 6. 更新订单状态为成功
        order.setStatus(OrderStatus.SUCCESS);
        orderService.updateOrder(order);

        log.info("订单创建流程完成, 订单ID: {}", order.getId());
        return order;
    }
}

/**
 * 账户服务
 * @author ken
 */
@Service
@Slf4j
public class AccountService {
    @Autowired
    private AccountMapper accountMapper;

    /**
     * 扣减用户余额
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void reduceBalance(Long userId, BigDecimal amount) {
        log.info("扣减用户余额, 用户ID: {}, 金额: {}", userId, amount);
        Account account = accountMapper.selectById(userId);
        if (account.getBalance().compareTo(amount) < 0) {
            throw new InsufficientBalanceException("余额不足");
        }
        account.setBalance(account.getBalance().subtract(amount));
        accountMapper.updateById(account);
    }
}
代码语言:javascript
复制

各服务的事务传播配置

  • OrderProcessService.createCompleteOrder():REQUIRED - 主事务
  • OrderService.createOrder():REQUIRED - 参与主事务
  • InventoryService.reduceStock():REQUIRED - 参与主事务
  • AccountService.reduceBalance():REQUIRED - 参与主事务
  • PointsService.addPoints():NESTED - 嵌套事务,失败可回滚但不影响主流程
  • LogService.recordLog():REQUIRES_NEW - 独立事务,确保日志被记录

这种配置的优势

  1. 核心业务(订单、库存、余额)在同一个事务中,确保数据一致性
  2. 积分操作使用嵌套事务,失败时只回滚积分部分,不影响核心业务
  3. 日志记录使用独立事务,无论主业务成功与否都能记录

4.2 分布式事务场景下的传播机制

在分布式系统中,多个服务之间的事务协调更为复杂。虽然 Spring 的事务传播机制主要针对单库事务,但可以与分布式事务解决方案结合使用。

代码语言:javascript
复制
/**
 * 分布式订单服务
 * 协调多个微服务完成订单创建
 * @author ken
 */
@Service
@Slf4j
public class DistributedOrderService {
    @Autowired
    private LocalOrderService localOrderService;

    @Autowired
    private RemoteInventoryService remoteInventoryService;

    @Autowired
    private RemotePaymentService remotePaymentService;

    @Autowired
    private RemoteNotificationService remoteNotificationService;

    /**
     * 创建分布式订单
     * 使用Seata分布式事务协调
     */
    @GlobalTransactional(rollbackFor = Exception.class) // Seata分布式事务注解
    public OrderDTO createDistributedOrder(OrderCreateDTO dto) {
        log.info("开始创建分布式订单: {}", dto);

        // 1. 创建本地订单
        OrderDTO order = localOrderService.createOrder(dto);

        try {
            // 2. 远程调用库存服务扣减库存
            remoteInventoryService.reduceStock(dto.getProductId(), dto.getQuantity());

            // 3. 远程调用支付服务处理支付
            PaymentResult result = remotePaymentService.processPayment(
                dto.getUserId(), order.getOrderId(), dto.getAmount());

            if (result.isSuccess()) {
                // 4. 更新订单状态为已支付
                localOrderService.updateOrderStatus(order.getOrderId(), OrderStatus.PAID);

                // 5. 远程调用通知服务发送消息(非关键步骤)
                try {
                    remoteNotificationService.sendPaymentSuccessNotification(
                        dto.getUserId(), order.getOrderId());
                } catch (Exception e) {
                    log.error("发送通知失败, 已记录但不影响主流程: {}", e.getMessage());
                }
            } else {
                throw new PaymentFailedException("支付失败: " + result.getErrorMessage());
            }
        } catch (Exception e) {
            log.error("分布式订单处理失败: {}", e.getMessage());
            // 抛出异常,触发全局回滚
            throw e;
        }

        log.info("分布式订单创建完成: {}", order.getOrderId());
        return order;
    }
}

/**
 * 本地订单服务
 * @author ken
 */
@Service
@Slf4j
public class LocalOrderService {
    @Autowired
    private OrderMapper orderMapper;

    /**
     * 创建本地订单
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public OrderDTO createOrder(OrderCreateDTO dto) {
        log.info("创建本地订单: {}", dto);
        // 订单创建逻辑...
    }

    /**
     * 更新订单状态
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void updateOrderStatus(Long orderId, OrderStatus status) {
        log.info("更新订单状态, 订单ID: {}, 状态: {}", orderId, status);
        // 状态更新逻辑...
    }
}

/**
 * 远程库存服务客户端
 * @author ken
 */
@FeignClient(name = "inventory-service")
public interface RemoteInventoryService {
    /**
     * 远程扣减库存
     */
    @PostMapping("/api/inventory/reduce")
    @Transactional(propagation = Propagation.MANDATORY) // 必须在分布式事务中运行
    void reduceStock(@RequestParam("productId") Long productId, 
                    @RequestParam("quantity") Integer quantity);
}
代码语言:javascript
复制

分布式场景下的事务传播策略

  1. 全局事务由@GlobalTransactional注解标记(以 Seata 为例)
  2. 本地服务方法使用REQUIRED传播行为,参与全局事务
  3. 远程服务方法使用MANDATORY传播行为,确保必须在全局事务中运行
  4. 非关键操作(如通知)可以使用REQUIRES_NEW或捕获异常,避免影响主流程

五、事务传播机制的常见问题与解决方案

5.1 自调用导致事务失效

问题描述:当一个事务方法调用同一个类中的另一个事务方法时,事务传播机制失效。

代码语言:javascript
复制
@Service
public class OrderService {
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder(Order order) {
        // 业务逻辑...
        // 调用同一个类中的另一个事务方法
        this.updateOrderStatus(order.getId(), OrderStatus.PENDING);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateOrderStatus(Long orderId, OrderStatus status) {
        // 更新订单状态...
    }
}
代码语言:javascript
复制

问题原因:Spring 事务通过 AOP 实现,自调用时不会经过 AOP 代理,因此事务注解失效。

解决方案

  1. 将方法拆分到不同的服务类中
  2. 自注入当前服务的代理对象
代码语言:javascript
复制
@Service
public class OrderService {
    // 注入自己的代理对象
    @Autowired
    private OrderService orderService;

    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder(Order order) {
        // 业务逻辑...
        // 通过代理对象调用方法
        orderService.updateOrderStatus(order.getId(), OrderStatus.PENDING);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateOrderStatus(Long orderId, OrderStatus status) {
        // 更新订单状态...
    }
}
代码语言:javascript
复制

5.2 异常被捕获导致事务不回滚

问题描述:当事务方法中的异常被捕获后,事务不会回滚。

代码语言:javascript
复制
@Service
public class OrderService {
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder(Order order) {
        try {
            orderMapper.insert(order);
            inventoryService.reduceStock(order.getProductId(), order.getQuantity());
        } catch (Exception e) {
            log.error("创建订单失败", e);
            // 异常被捕获,没有重新抛出
        }
    }
}
代码语言:javascript
复制

问题原因:Spring 事务通过检测未被捕获的异常来触发回滚,如果异常被捕获,事务管理器会认为方法执行成功。

解决方案:捕获异常后重新抛出,或手动触发回滚。

代码语言:javascript
复制
@Service
public class OrderService {
    @Autowired
    private TransactionStatus transactionStatus;

    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder(Order order) {
        try {
            orderMapper.insert(order);
            inventoryService.reduceStock(order.getProductId(), order.getQuantity());
        } catch (Exception e) {
            log.error("创建订单失败", e);
            // 方案1: 重新抛出异常
            throw new BusinessException("创建订单失败", e);

            // 方案2: 手动回滚
            // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    }
}
代码语言:javascript
复制

5.3 错误的异常类型导致事务不回滚

问题描述:抛出检查型异常时,事务不会回滚。

代码语言:javascript
复制
@Service
public class OrderService {
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder(Order order) throws IOException {
        orderMapper.insert(order);
        // 抛出检查型异常
        throw new IOException("文件操作失败");
    }
}
代码语言:javascript
复制

问题原因:Spring 默认只对未检查异常(继承自RuntimeException)和Error进行回滚,对检查型异常不回滚。

解决方案:指定rollbackFor属性,明确需要回滚的异常类型。

代码语言:javascript
复制
@Service
public class OrderService {
    // 指定需要回滚的异常类型
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void createOrder(Order order) throws IOException {
        orderMapper.insert(order);
        throw new IOException("文件操作失败");
    }
}
代码语言:javascript
复制

5.4 事务传播与隔离级别的组合问题

问题描述:不同事务传播行为与隔离级别组合时可能产生意想不到的结果。

解决方案:了解不同传播行为对隔离级别的影响:

  • REQUIRED和MANDATORY:使用外部事务的隔离级别
  • REQUIRES_NEW和NESTED:使用自己定义的隔离级别
  • SUPPORTS:如果加入外部事务,使用外部事务的隔离级别
  • NOT_SUPPORTED和NEVER:不使用事务,隔离级别无效

建议:核心事务使用明确的隔离级别,非核心事务可以使用默认设置。

六、事务传播机制的最佳实践

6.1 传播行为的选择原则

  1. 优先使用默认的 REQUIRED:大多数业务场景都适合使用REQUIRED,它能保证相关操作在同一个事务中。
  2. 独立操作使用 REQUIRES_NEW:日志记录、审计跟踪等需要独立存在的操作,应使用REQUIRES_NEW
  3. 关键操作使用 MANDATORY:对于必须在事务中执行的关键操作,使用MANDATORY确保不会在无事务环境中执行。
  4. 可选操作使用 SUPPORTS:对于查询类操作或非核心业务,使用SUPPORTS可以提高灵活性。
  5. 部分回滚需求使用 NESTED:当需要部分回滚能力时,使用NESTED,但要注意数据库是否支持保存点。
  6. 避免使用 NOT_SUPPORTED 和 NEVER:这两种传播行为会脱离事务管理,除非有特殊理由,否则应谨慎使用。

6.2 事务边界设计原则

  1. 事务边界应尽可能小:只将必要的操作包含在事务中,减少锁的持有时间,提高并发性能。
  2. 避免长事务:长时间运行的事务会导致数据库连接耗尽和锁竞争加剧,应拆分为多个短事务。
  3. 事务方法应专注于业务逻辑:避免在事务方法中执行耗时操作(如远程调用、IO 操作),可以将这些操作移到事务外。
  4. 明确事务的回滚策略:总是指定rollbackFor属性,明确哪些异常需要触发回滚。

6.3 性能优化建议

  1. 合理设置事务超时时间:根据业务复杂度设置合适的超时时间,避免事务长时间占用资源。
代码语言:javascript
复制
@Transactional(propagation = Propagation.REQUIRED, timeout = 30) // 超时时间30秒
public void processOrder(Order order) {
    // 业务逻辑...
}
代码语言:javascript
复制

  1. 查询操作使用只读事务:对于纯查询操作,设置readOnly = true可以提高性能,特别是对于 Hibernate 等 ORM 框架。
代码语言:javascript
复制
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public List<Order> queryOrders(Long userId) {
    // 查询逻辑...
}
代码语言:javascript
复制

  1. 避免在事务中进行批量操作:大批量操作会导致事务日志过大和锁竞争,应拆分或使用专门的批量处理机制。
  2. 合理使用缓存:将频繁访问的数据放入缓存,减少事务中的数据库操作。

七、总结:掌握事务传播,构建可靠系统

Spring 事务传播机制是保证数据一致性的关键技术,它通过定义不同的传播行为,解决了嵌套方法调用中的事务管理问题。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-10-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 果酱带你啃java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言:事务传播的 "隐形之手"
  • 一、事务与事务传播的基础概念
    • 1.1 什么是事务?
    • 1.2 什么是事务传播机制?
    • 1.3 Spring 中事务传播机制的定义
  • 二、Spring 事务传播机制的实现原理
    • 2.1 Spring 事务管理的核心组件
    • 2.2 事务传播的工作流程
    • 2.3 事务的绑定与传播
  • 三、7 种事务传播行为详解
    • 3.1 REQUIRED:默认的传播行为
    • 3.2 SUPPORTS:支持当前事务
    • 3.3 MANDATORY:必须在事务中运行
    • 3.4 REQUIRES_NEW:创建新事务
    • 3.5 NOT_SUPPORTED:不支持事务
    • 3.6 NEVER:不允许事务
    • 3.7 NESTED:嵌套事务
  • 四、事务传播机制的实战案例
    • 4.1 订单创建全流程事务管理
    • 4.2 分布式事务场景下的传播机制
  • 五、事务传播机制的常见问题与解决方案
    • 5.1 自调用导致事务失效
    • 5.2 异常被捕获导致事务不回滚
    • 5.3 错误的异常类型导致事务不回滚
    • 5.4 事务传播与隔离级别的组合问题
  • 六、事务传播机制的最佳实践
    • 6.1 传播行为的选择原则
    • 6.2 事务边界设计原则
    • 6.3 性能优化建议
  • 七、总结:掌握事务传播,构建可靠系统
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档