spring 事务的传播级别

发布于:2025-06-18 ⋅ 阅读:(15) ⋅ 点赞:(0)

简介

Spring 事务传播机制定义了当一个事务方法被另一个事务方法调用时,事务应该如何传播。它是 Spring 事务管理 (@Transactional) 的核心特性之一,决定了多个事务操作在相互调用时的边界和行为。

Spring 支持以下 7 种传播行为
(定义在 org.springframework.transaction.annotation.Propagation 枚举中):
这里无论是声明性事务还是编程性事务都是这7个级别,都可以设置

/*
 * Copyright 2002-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.transaction.annotation;

import org.springframework.transaction.TransactionDefinition;

/**
 * Enumeration that represents transaction propagation behaviors for use
 * with the {@link Transactional} annotation, corresponding to the
 * {@link TransactionDefinition} interface.
 *
 * @author Colin Sampaleanu
 * @author Juergen Hoeller
 * @since 1.2
 */
public enum Propagation {

	/**
	 * Support a current transaction, create a new one if none exists.
	 * Analogous to EJB transaction attribute of the same name.
	 * <p>This is the default setting of a transaction annotation.
	 */
	REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),

	/**
	 * Support a current transaction, execute non-transactionally if none exists.
	 * Analogous to EJB transaction attribute of the same name.
	 * <p>Note: For transaction managers with transaction synchronization,
	 * {@code SUPPORTS} is slightly different from no transaction at all,
	 * as it defines a transaction scope that synchronization will apply for.
	 * As a consequence, the same resources (JDBC Connection, Hibernate Session, etc)
	 * will be shared for the entire specified scope. Note that this depends on
	 * the actual synchronization configuration of the transaction manager.
	 * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization
	 */
	SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),

	/**
	 * Support a current transaction, throw an exception if none exists.
	 * Analogous to EJB transaction attribute of the same name.
	 */
	MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),

	/**
	 * Create a new transaction, and suspend the current transaction if one exists.
	 * Analogous to the EJB transaction attribute of the same name.
	 * <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
	 * on all transaction managers. This in particular applies to
	 * {@link org.springframework.transaction.jta.JtaTransactionManager},
	 * which requires the {@code jakarta.transaction.TransactionManager} to be
	 * made available to it (which is server-specific in standard Jakarta EE).
	 * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
	 */
	REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),

	/**
	 * Execute non-transactionally, suspend the current transaction if one exists.
	 * Analogous to EJB transaction attribute of the same name.
	 * <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
	 * on all transaction managers. This in particular applies to
	 * {@link org.springframework.transaction.jta.JtaTransactionManager},
	 * which requires the {@code jakarta.transaction.TransactionManager} to be
	 * made available to it (which is server-specific in standard Jakarta EE).
	 * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
	 */
	NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),

	/**
	 * Execute non-transactionally, throw an exception if a transaction exists.
	 * Analogous to EJB transaction attribute of the same name.
	 */
	NEVER(TransactionDefinition.PROPAGATION_NEVER),

	/**
	 * Execute within a nested transaction if a current transaction exists,
	 * behave like {@code REQUIRED} otherwise. There is no analogous feature in EJB.
	 * <p>Note: Actual creation of a nested transaction will only work on specific
	 * transaction managers. Out of the box, this only applies to the JDBC
	 * DataSourceTransactionManager. Some JTA providers might support nested
	 * transactions as well.
	 * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
	 */
	NESTED(TransactionDefinition.PROPAGATION_NESTED);


	private final int value;


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

	public int value() {
		return this.value;
	}

}

REQUIRED (默认)

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

理解: “有则共享,无则自创”。这是最常用、最自然的设置。它保证了多个操作要么都在同一个事务中成功提交,要么一起回滚。

场景: 绝大多数业务逻辑方法。例如,订单服务调用库存服务扣减库存,两者应在同一个事务中。

@Service
public class OrderService {
    @Autowired
    private InventoryService inventoryService;

    @Transactional(propagation = Propagation.REQUIRED) // 默认就是 REQUIRED, 可省略
    public void placeOrder(Order order) {
        // 1. 保存订单 (当前无事务,创建新事务Tx1)
        saveOrder(order);
        // 2. 调用库存服务 (当前已有事务Tx1,加入Tx1)
        inventoryService.deductStock(order.getProductId(), order.getQuantity());
        // ... 其他业务逻辑 ...
    } // 整个placeOrder方法成功执行后,事务Tx1提交。如果任何地方抛出RuntimeException,Tx1回滚。
}

@Service
public class InventoryService {
    @Transactional(propagation = Propagation.REQUIRED)
    public void deductStock(Long productId, int quantity) {
        // ... 扣减库存逻辑 ... (在调用者的事务Tx1中执行)
    }
}

REQUIRIES_NEW

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

理解: “另起炉灶,不受影响”。新事务与调用者的事务完全独立,互不干扰。新事务有自己的提交/回滚。

场景: 需要独立提交、不受外层事务成败影响的子操作。例如:记录审计日志(即使主业务失败,审计日志仍需记录)、调用外部系统(需要确保调用记录)。

@Transactional(propagation = Propagation.REQUIRED)
public void outerMethod() {
    // ... 外层业务逻辑 (事务Tx1) ...
    try {
        auditService.logAction(); // 调用审计服务 (开启新事务Tx2, Tx1被挂起)
    } catch (Exception e) {
        // 处理审计日志异常,避免影响外层事务
    }
    // ... 继续外层业务逻辑 (Tx1恢复) ...
    // 可能出错导致Tx1回滚
} // 无论Tx1最终提交还是回滚,Tx2(如果成功执行完)已独立提交。

@Service
public class AuditService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logAction() {
        // ... 记录审计日志 ... (在独立的事务Tx2中执行)
    } // Tx2 在此提交(如果没有异常)
}

SUPPORTS

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

理解: “随遇而安”。方法本身不强制要求事务,但有就用,没有就算了。

场景: 查询方法、日志记录等非核心业务逻辑,可以容忍数据在事务内外不一致的短暂情况(如只读查询)。

@Service
public class BusinessService {

    @Autowired
    private LogService logService; // 注入日志服务Bean

    @Transactional(propagation = Propagation.REQUIRED)
    public void businessMethod() {
        // ... 核心业务逻辑 (在事务Tx1中) ...
        logService.logOperation(); // 调用另一个Bean的方法,会通过代理
    }
}

@Service
public class LogService {

    @Transactional(propagation = Propagation.SUPPORTS)
    public void logOperation() {
        // 现在这个调用会经过Spring代理,传播行为SUPPORTS生效
        // 如果被businessMethod (有事务) 调用,则加入Tx1
        // 如果被无事务方法调用,则无事务执行
    }
}

NOT_SUPPORTED

含义: 以非事务方式执行操作。如果当前存在事务,则将当前事务挂起。

理解: “拒绝加入,暂停你们”。该方法明确要求不在事务中运行,并暂停调用者的任何现有事务。

场景: 需要在事务外执行的操作,比如某些性能敏感且不需要事务保证的批量处理、调用不支持事务的资源(如某些 NoSQL)。慎用,因为它会破坏事务边界。


@Service
@RequiredArgsConstructor
public class DataExportService {
    private final ExportProcessor exportProcessor;
    private final StatsService statsService;

    @Transactional
    public void exportTransactionalData() {
        // 事务操作(Tx1)
        List<Data> data = dataRepository.findRecent();
        
        // 挂起事务执行导出
        exportProcessor.exportLargeData(data); // 挂起 Tx1
        
        // 更新统计(在恢复的事务 Tx1 中)
        statsService.updateExportStats();
    }
}

@Service
public class ExportProcessor {
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void exportLargeData(List<Data> data) {
        // 在无事务上下文中执行(Tx1 被挂起)
        // 长时间操作,不需要事务保证
        data.forEach(item -> externalSystem.send(item));
    }
}

MANDATORY

含义: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常(IllegalTransactionStateException)。

理解: “必须在事务中运行”。该方法强制要求在一个已存在的事务上下文中被调用。

场景: 某些必须作为更大事务一部分执行的关键操作。确保调用者已经开启了事务。

@Service
@RequiredArgsConstructor
public class AuditService {
    private final AuditLogService auditLogService;

    @Transactional  // 确保在事务中调用
    public void auditAction(String action) {
        // 必须在事务中记录审计日志
        auditLogService.logAction(action);
    }
}

@Service
public class AuditLogService {
    @Transactional(propagation = Propagation.MANDATORY)
    public void logAction(String action) {
        // 如果被非事务方法调用,将抛出 IllegalTransactionStateException
        auditLogRepository.save(new AuditLog(action));
    }
}

NEVER

含义: 以非事务方式执行。如果当前存在事务,则抛出异常(IllegalTransactionStateException)。

理解: “禁止在事务中运行”。该方法要求绝对不能在事务上下文中被调用。

场景: 某些明确不能有事务参与的操作(虽然少见)。确保方法执行时没有事务干扰。

// ===================== 6. NEVER =====================
@Service
@RequiredArgsConstructor
public class CacheWarmupService {
    private final CacheLoader cacheLoader;

    // 非事务方法
    public void warmupCaches() {
        // 必须在非事务环境中执行
        cacheLoader.preloadUserCache();
    }
}

@Service
public class CacheLoader {
    @Transactional(propagation = Propagation.NEVER)
    public void preloadUserCache() {
        // 如果被事务方法调用,将抛出 IllegalTransactionStateException
        List<User> users = userRepository.findAll();
        cacheManager.load("users", users);
    }
}

NESTED

含义: 如果当前存在事务,则在当前事务的一个嵌套事务中执行。嵌套事务是外层事务的一部分,但可以有自己的保存点。如果当前没有事务,其行为与 REQUIRED 一样(创建一个新事务)。

理解: “子事务”。嵌套事务的提交依赖于外层事务的提交。只有外层事务提交时,嵌套事务的修改才会最终提交。嵌套事务可以独立回滚到其保存点,而不影响外层事务(除非回滚到保存点之前);但外层事务回滚会导致整个嵌套事务回滚。

关键点:

依赖于底层数据库或 JDBC 驱动支持保存点(大多数主流数据库都支持)。

不是 JTA 标准的一部分,主要是特定于某些资源(如 JDBC)的概念。

与 REQUIRES_NEW 的区别: REQUIRES_NEW 创建的是完全独立的、物理上的新事务。NESTED 创建的是逻辑上的子事务(利用保存点实现),是外层事务的一部分。

// ===================== 7. NESTED =====================
@Service
@RequiredArgsConstructor
public class BatchProcessor {
    private final ItemProcessor itemProcessor;

    @Transactional
    public void processBatch(List<Item> items) {
        batchRepository.startBatch();
        
        for (Item item : items) {
            try {
                // 每个项目在嵌套事务中处理
                itemProcessor.processItem(item);
            } catch (ItemProcessingException e) {
                // 单个项目失败不影响整体批次
                errorRepository.logError(item, e);
            }
        }
        
        batchRepository.completeBatch();
    }
}

@Service
public class ItemProcessor {
    @Transactional(propagation = Propagation.NESTED)
    public void processItem(Item item) {
        // 嵌套事务(使用保存点)
        // 失败时只回滚此操作,不影响外层事务
        itemRepository.process(item);
        inventoryService.adjustStock(item);
    }
}

总结

REQUIRED (默认): 最常用,保证方法在事务中运行(加入或新建)。

REQUIRES_NEW: 需要绝对独立提交/回滚时使用(如审计日志)。会挂起当前事务。

SUPPORTS/NOT_SUPPORTED: 灵活适应调用环境(有就用/拒绝用)。

MANDATORY/NEVER: 强制约束调用环境(必须/禁止事务)。

NESTED: 需要子操作部分失败不影响整体时使用(利用保存点)。是外层事务的一部分。需要数据库支持保存点。

事务的自调用问题

事务的自调用会让事务失效
可以参考这篇文章spring事务的自调用问题

上面这篇文章 只举了一个例子

声明性事务自调用问题

简单来说就是自调用的事务你不管用的什么传播级别,都会失效

public void outer() {
    this.innerNotSupported(); // 自调用,NOT_SUPPORTED 失效
}

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void innerNotSupported() {
    // 预期:挂起当前事务(如果有)
    // 实际:注解失效,方法在调用者的事务上下文中执行
}

如果outer也有事务
那么innerNotSupported这事务标注的方法就会失效,加入到outer里面的事务


@Transactional(propagation = Propagation.REQUIRED )
public void outer() {
    this.innerNotSupported(); // 自调用,NOT_SUPPORTED 失效
}

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void innerNotSupported() {
    // 预期:挂起当前事务(如果有)
    // 实际:注解失效,方法在调用者的事务上下文中执行
}

编程性事务没有自调用问题

public void outerMethod() {
    // 编程式事务无自调用问题
    transactionTemplate.execute(status -> {
        innerNeverMethod(); // 直接调用
        return null;
    });
}

private void innerNeverMethod() {
    // 因为事务边界由TransactionTemplate明确控制
    // 不需要@Transactional注解也可实现事务控制
}

总结

多个事务联合调用的时候,自调用主要的特点:
1.主事务不受影响
@Transactional 修饰的外部入口方法的事务仍然有效
2.内部方法事务特性完全丢失
传播行为/隔离级别/超时设置等全部失效
3.但仍在主事务中执行
这是最隐蔽的危险:方法仍在主事务上下文运行,但失去了事务配置的保护

解决方案:

在这里插入图片描述
这里其实最常见的就是服务拆分,使用代理


网站公告

今日签到

点亮在社区的每一天
去签到