乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 Spring概述与IoC容器

    • Spring概述与IoC容器
    • Spring Framework 概述
    • IoC 与 DI 核心概念
    • @Configuration 详解
    • @Component 详解
    • @ComponentScan 详解
    • @Import 详解
    • @Profile 详解
    • @PropertySource 详解
    • @Service 详解
    • @Repository 详解
  • 第2章 Bean的定义与依赖注入

    • Bean的定义与依赖注入
    • @Bean 详解
    • @Autowired 详解
    • @Qualifier 详解
    • @Primary 详解
    • @Resource 详解
    • @Inject 详解
    • @Named 详解
    • @Value 详解
    • @Scope 详解
    • @Lazy 详解
  • 第3章 Bean生命周期与作用域

    • Bean生命周期与作用域
    • Bean生命周期概述
    • @PostConstruct
    • @PreDestroy
    • InitializingBean
    • DisposableBean
    • BeanPostProcessor
    • BeanFactoryPostProcessor
  • 第4章 AOP面向切面编程

    • AOP面向切面编程
    • AOP核心概念
    • @EnableAspectJAutoProxy
    • @Aspect
    • @Pointcut
    • @Before
    • @After
    • @AfterReturning
    • @AfterThrowing
    • @Around
  • 第5章 数据访问与事务管理

    • 数据访问与事务管理
    • 数据访问概述
    • @EnableTransactionManagement
    • @Transactional
    • @Transactional 的传播行为
    • @Transactional 的隔离级别
    • @Transactional 的回滚规则
    • @Transactional 的超时与只读属性
    • @TransactionalEventListener
  • 第6章 Spring Boot自动配置基础

    • Spring Boot自动配置基础
    • @SpringBootApplication 注解
    • @EnableAutoConfiguration 注解
    • @ConfigurationProperties 注解
    • @ConditionalOnClass 注解
    • @ConditionalOnMissingBean 注解
    • @ConditionalOnProperty 注解
  • 第7章 从容器到Web: Spring MVC导引

    • Spring MVC 导引
  • 第8章 扩展阅读

    • 扩展阅读
    • Spring 事件机制 — ApplicationEvent / ApplicationListener
    • @EventListener
    • SpEL — Spring 表达式语言
    • 校验 Validation — JSR-303 / JSR-380 Bean Validation
    • 类型转换与数据绑定 — Converter / DataBinder
  • 附录

    • Spring Framework 专业术语
    • Spring 核心知识点
    • Spring 面试高频考点
    • Spring 核心注解速查表

@Transactional 的传播行为

定义与作用

propagation 是 @Transactional 的核心属性之一,用于定义当前方法的事务边界如何与已存在的事务上下文交互。在多层 Service 调用的企业级应用中,不同业务方法对事务的需求各不相同:有些方法需要独立提交,有些方法需要加入外层事务,有些方法则明确要求在无事务环境中执行。

Spring 将事务传播行为抽象为 7 种策略,由 org.springframework.transaction.annotation.Propagation 枚举定义。理解传播行为是掌握 Spring 事务管理的关键,也是面试中区分"会用"与"精通"的分水岭。

适用位置与常用属性

propagation 属性只能在 @Transactional 注解的方法级别或类级别使用,与 isolation、timeout 等属性并列配置:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void auditLog(String operation) { ... }

七种传播行为对比表

传播行为当前无事务当前有事务核心语义
REQUIRED(默认)新建事务加入当前事务"随大流",最常用
REQUIRES_NEW新建事务挂起当前事务,新建独立事务"我行我素",必须独立提交
SUPPORTS非事务执行加入当前事务"可有可无",查询方法适用
NOT_SUPPORTED非事务执行挂起当前事务,非事务执行"拒绝事务",发送通知适用
MANDATORY抛出 IllegalTransactionStateException加入当前事务"强制要求",必须在外层事务中调用
NEVER非事务执行抛出 IllegalTransactionStateException"严禁事务",纯查询适用
NESTED新建事务在当前事务中创建 savepoint 嵌套事务"局部回滚",部分失败可独立回滚

重要区别:REQUIRES_NEW 是挂起外层事务后创建完全独立的新事务,两个事务的提交/回滚互不影响。NESTED 是在当前事务中创建一个savepoint,嵌套事务回滚时只回滚到 savepoint,外层事务仍可继续提交。

核心原理:传播行为的判定流程

Spring 的 TransactionInterceptor 在方法调用前,通过 PlatformTransactionManager.getTransaction() 根据传播行为决定事务策略:

REQUIRED vs REQUIRES_NEW 时序图

关键观察:REQUIRES_NEW 的审计日志事务(Tx2)在订单事务(Tx1)提交前就已经独立提交。即使后续 Tx1 回滚,审计日志仍然保留。

NESTED 的 savepoint 机制

数据库支持限制:NESTED 依赖数据库的 savepoint 支持(JDBC 3.0+)。若底层驱动不支持 savepoint,Spring 会降级为 REQUIRES_NEW 行为。

完整示例:飞翔科技订单与审计系统

场景简述

飞翔科技公司的电商平台有两个核心服务:

  • OrderService:处理订单创建,需要事务保证订单、库存、余额的一致性;
  • AuditService:记录操作审计日志,无论订单是否成功,日志都必须持久化。

小崔(后端开发)需要为这两个服务配置不同的事务传播行为,以满足业务需求。

操作前:数据库表结构与初始数据

-- 订单表
CREATE TABLE orders (
    order_id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    product_name VARCHAR(100),
    amount DECIMAL(10,2),
    status VARCHAR(20)
);

-- 库存表
CREATE TABLE inventory (
    product_id INT PRIMARY KEY,
    stock INT NOT NULL
);

-- 审计日志表
CREATE TABLE audit_log (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    operation VARCHAR(100),
    detail TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 初始数据
INSERT INTO inventory (product_id, stock) VALUES (10086, 10);

Service 层:不同传播行为的配置

package com.feixiang.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private AuditService auditService;

    // 订单创建:REQUIRED(默认),加入或新建事务
    @Transactional
    public void createOrder(int userId, int productId, String productName, double amount) {
        // 1. 扣减库存
        int updated = jdbcTemplate.update(
            "UPDATE inventory SET stock = stock - 1 WHERE product_id = ? AND stock > 0",
            productId
        );
        if (updated == 0) {
            throw new IllegalStateException("库存不足,商品ID:" + productId);
        }

        // 2. 插入订单
        jdbcTemplate.update(
            "INSERT INTO orders (user_id, product_name, amount, status) VALUES (?, ?, ?, ?)",
            userId, productName, amount, "CREATED"
        );
        System.out.println("[OrderService] 订单创建成功,用户:" + userId);

        // 3. 记录审计日志(独立事务,不受订单回滚影响)
        auditService.logOperation("CREATE_ORDER", "用户" + userId + "创建订单,商品:" + productName);

        // 4. 模拟异常:触发订单回滚,但审计日志已独立提交
        if (amount > 10000) {
            throw new RuntimeException("订单金额超限,触发回滚测试");
        }
    }
}
package com.feixiang.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class AuditService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    // 审计日志:REQUIRES_NEW,独立事务,必须提交
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logOperation(String operation, String detail) {
        jdbcTemplate.update(
            "INSERT INTO audit_log (operation, detail) VALUES (?, ?)",
            operation, detail
        );
        System.out.println("[AuditService] 审计日志已记录:" + operation);
    }

    // 查询审计日志:SUPPORTS,有事务则加入,无则非事务查询
    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public int countLogs(String operation) {
        return jdbcTemplate.queryForObject(
            "SELECT COUNT(*) FROM audit_log WHERE operation = ?",
            Integer.class, operation
        );
    }

    // 发送通知:NOT_SUPPORTED,挂起事务,非事务执行
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void sendNotification(String message) {
        // 模拟发送短信/邮件,不需要事务
        System.out.println("[Notification] 发送通知:" + message);
    }
}

测试执行与结果

package com.feixiang;

import com.feixiang.service.AuditService;
import com.feixiang.service.OrderService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class PropagationTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx =
            new AnnotationConfigApplicationContext("com.feixiang");
        OrderService orderService = ctx.getBean(OrderService.class);
        AuditService auditService = ctx.getBean(AuditService.class);

        // 场景一:正常订单(金额 200,不超限)
        try {
            orderService.createOrder(2024001, 10086, "机械键盘", 200.00);
        } catch (Exception e) {
            System.err.println("场景一异常:" + e.getMessage());
        }

        // 场景二:金额超限,触发订单回滚,观察审计日志是否保留
        try {
            orderService.createOrder(2024002, 10086, "高端显卡", 20000.00);
        } catch (Exception e) {
            System.err.println("场景二异常:" + e.getMessage());
        }

        // 场景三:查询审计日志数量
        int logCount = auditService.countLogs("CREATE_ORDER");
        System.out.println("[查询结果] CREATE_ORDER 审计日志数量:" + logCount);

        ctx.close();
    }
}

操作后:控制台输出与数据变化

场景一(正常订单)控制台输出:

[OrderService] 订单创建成功,用户:2024001
[AuditService] 审计日志已记录:CREATE_ORDER

场景一数据库状态:

orders 表:

order_iduser_idproduct_nameamountstatus
12024001机械键盘200.00CREATED

inventory 表:

product_idstock
100869

audit_log 表:

idoperationdetailcreated_at
1CREATE_ORDER用户2024001创建订单,商品:机械键盘...

场景二(金额超限,触发回滚)控制台输出:

[OrderService] 订单创建成功,用户:2024002
[AuditService] 审计日志已记录:CREATE_ORDER
场景二异常:订单金额超限,触发回滚测试

场景二数据库状态:

orders 表:无新增记录(订单回滚)。

inventory 表:

product_idstock
100869(未变化,回滚生效)

audit_log 表:

idoperationdetailcreated_at
1CREATE_ORDER用户2024001创建订单,商品:机械键盘...
2CREATE_ORDER用户2024002创建订单,商品:高端显卡...

关键验证:尽管订单事务回滚了,但 audit_log 表中的第二条记录仍然存在。这证明了 REQUIRES_NEW 的审计日志事务是独立提交的,不受外层订单事务回滚的影响。

场景三(查询审计日志):

[查询结果] CREATE_ORDER 审计日志数量:2

SUPPORTS 传播行为在此场景下以非事务方式执行查询,因为没有外层事务上下文。

易错场景与面试考点

反例一:误用 REQUIRED 导致审计日志随主事务回滚

@Service
public class OrderService {

    @Autowired
    private AuditService auditService;

    @Transactional
    public void createOrder(...) {
        // ... 订单逻辑
        auditService.logOperation("CREATE_ORDER", detail);  // AuditService 使用 REQUIRED
        // ... 后续异常导致回滚
    }
}

@Service
public class AuditService {

    // 错误:使用默认 REQUIRED,审计日志会加入外层事务
    @Transactional
    public void logOperation(String operation, String detail) {
        // 插入审计日志
    }
}

问题分析:AuditService.logOperation() 使用默认的 REQUIRED,会加入 OrderService 的现有事务。当订单事务回滚时,审计日志也会被回滚,导致"操作发生了但无记录"的合规风险。审计日志必须独立持久化,应使用 REQUIRES_NEW。

反例二:REQUIRES_NEW 的异常向上传播导致外层事务回滚

@Service
public class OrderService {

    @Transactional
    public void createOrder(...) {
        // ... 订单逻辑
        auditService.logOperation("CREATE_ORDER", detail);
        // 如果 logOperation 抛出异常,异常会向上传播到 OrderService
        // 导致 OrderService 的事务也回滚!
    }
}

@Service
public class AuditService {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logOperation(String operation, String detail) {
        // 如果此处抛出异常...
        throw new RuntimeException("审计系统故障");
    }
}

问题分析:REQUIRES_NEW 确保内层事务独立提交,但无法隔离异常传播。如果 AuditService 抛出的异常未被 OrderService catch,异常会继续向上传播,触发 Spring 的默认回滚规则(RuntimeException),导致外层订单事务也回滚。

正确写法:

@Transactional
public void createOrder(...) {
    // ... 订单逻辑
    try {
        auditService.logOperation("CREATE_ORDER", detail);
    } catch (Exception e) {
        // 审计失败不影响主业务,记录日志即可
        System.err.println("审计记录失败:" + e.getMessage());
    }
    // ... 继续执行
}

反例三:NESTED 在不支持 savepoint 的数据库上降级

@Service
public class PaymentService {

    @Transactional(propagation = Propagation.NESTED)
    public void processPartialPayment(...) {
        // 期望:失败时只回滚这部分,外层继续
    }
}

问题分析:NESTED 依赖 JDBC savepoint。某些旧版数据库驱动或特定数据源(如某些连接池的 XA 场景)可能不支持 savepoint。此时 Spring 会降级为 REQUIRES_NEW,行为从"局部回滚"变为"完全独立事务",可能导致与预期不符的数据状态。生产环境使用 NESTED 前,必须验证底层驱动的 savepoint 支持。

面试高频考点

Q1:REQUIRED 和 REQUIRES_NEW 的本质区别是什么?

REQUIRED 是"加入或新建":有事务则加入,无则新建,内外层共享同一个物理事务,同生共死。REQUIRES_NEW 是"挂起并新建":无论当前是否有事务,都创建全新的独立事务,外层事务被挂起,内层事务独立提交或回滚,互不影响。

Q2:NESTED 与 REQUIRES_NEW 有什么区别?

NESTED 在当前事务中创建一个 savepoint,嵌套事务回滚时只回滚到该 savepoint,外层事务仍可继续并提交。REQUIRES_NEW 是创建完全独立的物理事务,两个事务有各自的事务状态和资源。NESTED 需要数据库支持 savepoint,且内层异常若未被捕获仍会触发外层回滚;REQUIRES_NEW 的内层事务提交不受外层影响,但异常传播仍可能触发外层回滚。

Q3:MANDATORY 的典型使用场景是什么?

MANDATORY 要求调用方必须已经开启事务,否则抛出异常。典型场景是核心业务校验方法:例如订单金额校验、库存预占等操作,必须在外层事务的保护下执行,防止非事务调用导致数据不一致。它起到一种"契约检查"的作用,强制上层调用者提供事务上下文。

Q4:SUPPORTS 和 NOT_SUPPORTED 有什么区别?

SUPPORTS 是"随遇而安":有事务则加入,无则非事务执行,常用于查询方法。NOT_SUPPORTED 是"主动排斥":有事务则挂起,无则非事务执行,常用于不需要事务且不希望被外层事务拖累的操作(如发送通知、调用外部 HTTP 接口)。NOT_SUPPORTED 会显式挂起现有事务,而 SUPPORTS 不会。

上一页
@Transactional
下一页
@Transactional 的隔离级别