关于事务的传播方式

传播行为定义了当一个事务方法被另一个事务方法调用时,事务应该如何进行。它决定了新方法是加入已有的“事务组”,还是自己单开一个“新项目组”,或者干脆不参与“项目组”(事务)。

核心传播行为详解与示例:

假设我们有两个服务方法:ServiceA.methodA()ServiceB.methodB()methodA 内部调用了 methodB

1.REQUIRED (默认) - “一起干”或“开新组”

含义: 如果当前存在事务则加入该事务;如果当前没有事务,则新建一个事务。

场景: 最常用的设置。适用于大多数业务逻辑,保证操作在同一个事务边界内。你在项目组A (methodA)。现在需要完成一个子任务 (methodB)。如果项目组A已经存在(有事务),你就直接在组A内完成子任务(加入事务)。如果还没有项目组(无事务),你就自己成立一个新项目组(新建事务)来完成这个子任务(以及后续可能由methodA做的其他事)。

        @Service
        public class ServiceA {
            @Autowired
            private ServiceB serviceB;

            @Transactional(propagation = Propagation.REQUIRED) // 默认就是REQUIRED
            public void methodA() {
                // 操作A
                serviceB.methodB(); // 调用另一个事务方法
                // 操作C(如果methodB失败回滚,操作A和操作C也会回滚)
            }
        }

        @Service
        public class ServiceB {
            @Transactional(propagation = Propagation.REQUIRED) // 加入ServiceA开启的事务
            public void methodB() {
                // 操作B
            }
        }
结果: `操作A`, `操作B`, `操作C` 要么一起成功,要么一起失败回滚。它们在一个物理数据库事务中执行。

2. REQUIRES_NEW - “必须单干”

含义: 总是新建一个事务。如果当前存在事务,则挂起(暂停) 当前事务,先执行新事务。新事务执行完毕(提交或回滚)后,恢复被挂起的事务。

场景: 需要独立于调用者的事务逻辑,无论调用者成功与否,该方法的操作都必须独立提交或回滚。常用于日志记录(即使主业务失败,日志也要记录)、需要强独立性的核心操作。

你是项目组A (methodA) 的成员。现在有一个非常重要的特殊任务 (methodB),要求必须单独成立一个项目组来完成,不受主项目组A成功或失败的影响。你暂停了组A的工作(挂起事务),成立新组B(新建事务)去完成任务。完成后(无论B组成功失败),再回到组A继续工作(恢复事务)。

        @Service
        public class ServiceA {
            @Autowired
            private ServiceB serviceB;
            @Autowired
            private AuditLogService auditLogService; // 假设这是一个记录审计日志的服务

            @Transactional(propagation = Propagation.REQUIRED)
            public void methodA() {
                // 操作A (在事务T1中)
                try {
                    serviceB.criticalOperation(); // REQUIRES_NEW, 开启新事务T2
                } catch (Exception e) {
                    // 即使criticalOperation失败,日志也要记录
                }
                auditLogService.log("methodA executed"); // REQUIRES_NEW, 开启新事务T3
                // 操作C (在事务T1中)
            }
        }

        @Service
        public class ServiceB {
            @Transactional(propagation = Propagation.REQUIRES_NEW)
            public void criticalOperation() {
                // 操作B (在独立的事务T2中)
            }
        }

        @Service
        public class AuditLogService {
            @Transactional(propagation = Propagation.REQUIRES_NEW) // 确保日志记录独立提交
            public void log(String message) {
                // 记录日志到数据库 (在独立的事务T3中)
            }
        }
结果:`操作A` 和 `操作C` 在事务 T1 中。 

`操作B` 在事务 T2 中。 * 审计日志记录在事务 T3 中。

* 即使 `操作B` (`criticalOperation`) 失败导致 T2 回滚,T1(包含操作A、操作C)和 T3(日志)不受影响(除非 `criticalOperation` 抛出的异常被 `methodA` 捕获后导致 T1 也失败)。

* 即使 T1(操作A或操作C失败)回滚,T2(操作B)和 T3(日志)**已经提交成功**(只要它们自己执行期间没出错)。

3. SUPPORTS - “随大流”

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

* 场景: 方法可以适应调用者的事务状态。主要用于查询方法,这些方法在有事务时保证读取一致性,在没有事务时也能执行(但可能读到中间状态)。慎用于修改数据的方法

        @Transactional(propagation = Propagation.REQUIRED)
        public void updateData() {
            // 更新操作1 (在事务中)
            String currentValue = readCurrentValue(); // SUPPORTS, 加入事务,读到更新操作1后的值?
            // 更新操作2 (在事务中,依赖currentValue)
        }

        @Transactional(propagation = Propagation.SUPPORTS)
        public String readCurrentValue() {
            // 读取数据
        }

* 如果 `updateData` 在一个事务中调用 `readCurrentValue`,那么 `readCurrentValue` 就在同一个事务中执行,可能读到 `updateData` 中已经修改但未提交的数据(取决于隔离级别)。如果直接调用 `readCurrentValue`(无事务环境),它就以普通查询执行。

4. NOT_SUPPORTED - “绝不参与” *

含义: 总是以非事务方式执行。如果当前存在事务,则挂起该事务。

* 场景: 方法本身不适合在事务中运行(例如,执行长时间计算、调用不支持事务的外部系统)。它会强制让调用它的方法暂时“脱离”事务环境执行它。

        @Transactional(propagation = Propagation.REQUIRED)
        public void processOrder() {
            // 事务性操作...
            externalSystemCall(); // NOT_SUPPORTED, 暂停当前事务,无事务执行
            // 恢复事务,继续其他事务操作...
        }

        @Transactional(propagation = Propagation.NOT_SUPPORTED)
        public void externalSystemCall() {
            // 调用一个不支持事务或执行非常慢的外部系统接口
        }


 `externalSystemCall` 的执行不会在数据库事务中进行,即使它失败了,也不会导致 `processOrder` 的事务回滚(除非它抛出的异常没有被处理)。

`processOrder` 的事务在调用 `externalSystemCall` 时被挂起,调用结束后恢复。

5. MANDATORY - “必须一起干”

 含义: 方法必须在一个已存在的事务中运行。如果当前没有事务,则抛出异(IllegalTransactionStateException)。

*场景: 严格要求方法不能独立运行,必须作为更大事务的一部分。用于强制事务上下文存在。 * 


        public void businessFlow() {
            // 没有开启事务
            try {
                serviceB.mandatoryOperation(); // 调用MANDATORY方法
            } catch (IllegalTransactionStateException e) {
                // 因为没有事务,这里会捕获到异常
            }
        }

        @Service
        public class ServiceB {
            @Transactional(propagation = Propagation.MANDATORY)
            public void mandatoryOperation() {
                // 必须在一个已存在的事务中运行
            }
        }

* 如果 `businessFlow` 没有用 `@Transactional` 标注(或者不在其他事务方法内),直接调用 `mandatoryOperation` 会导致异常。

6. NEVER - “绝不一起干”

 含义: 方法绝不能在一个事务中运行。如果当前存在事务,则抛出异常 (IllegalTransactionStateException)。

场景: 强制要求方法在非事务环境下执行,防止意外加入现有事务导致预期外的行为(如延迟提交、锁持有时间过长)。通常用于纯查询且对实时性要求极高(不希望被前面的事务阻塞)、或操作不支持事务的资源。

        @Transactional(propagation = Propagation.REQUIRED)
        public void updateAndCheck() {
            updateData(); // 更新操作
            try {
                realtimeStatsCheck(); // 调用NEVER方法
            } catch (IllegalTransactionStateException e) {
                // 因为updateAndCheck在事务中,这里会捕获到异常
            }
        }

        @Transactional(propagation = Propagation.NEVER)
        public void realtimeStatsCheck() {
            // 需要实时查询最新统计数据(不希望被updateData的事务阻塞)
        }
在事务方法 `updateAndCheck` 中调用 `realtimeStatsCheck` 会抛出异常。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值