找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4456

积分

0

好友

609

主题
发表于 2 小时前 | 查看: 2| 回复: 0

面试官:“请说一下Spring事务的传播行为有哪些?REQUIRED和REQUIRES_NEW的区别是什么?事务在什么情况下会失效?你在项目中遇到过事务失效的问题吗?如何排查?”

:(有条不紊地从事务传播行为开始,结合实际案例层层剖析...)

考察重点

这道题是 Spring面试的必考题,面试官想考察:

  1. 原理理解:对事务传播行为、隔离级别的掌握
  2. 源码功底:是否了解Spring事务的实现原理(AOP、TransactionInterceptor)
  3. 实战能力:能否列举事务失效的场景并分析原因
  4. 架构视野:对分布式事务的理解和解决方案

一、Spring事务核心原理

1.1 事务管理架构

┌─────────────────────────────────────────────────────────────┐
│                    Spring事务管理架构                         │
├─────────────────────────────────────────────────────────────┤
│  PlatformTransactionManager(事务管理器接口)                 │
│    ├── DataSourceTransactionManager(JDBC)                 │
│    ├── JtaTransactionManager(JTA)                         │
│    └── HibernateTransactionManager(Hibernate)             │
├─────────────────────────────────────────────────────────────┤
│  TransactionDefinition(事务定义)                           │
│    ├── 传播行为(Propagation)                               │
│    ├── 隔离级别(Isolation)                                │
│    ├── 超时时间(Timeout)                                  │
│    └── 只读状态(ReadOnly)                                 │
├─────────────────────────────────────────────────────────────┤
│  TransactionStatus(事务状态)                               │
│    ├── 是否是新事务                                         │
│    ├── 是否有保存点                                         │
│    └── 是否已回滚                                           │
└─────────────────────────────────────────────────────────────┘

1.2 事务执行流程

Spring事务通过AOP实现,核心拦截器是 TransactionInterceptor

// TransactionInterceptor.invoke()
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
    // 获取目标方法的事务属性
    TransactionAttribute txAttr = getTransactionAttributeSource()
            .getTransactionAttribute(method, targetClass);

    // 获取事务管理器
    PlatformTransactionManager tm = getTransactionManager();

    // 执行事务
    return invokeWithinTransaction(invocation, txAttr, tm);
}

二、事务传播行为详解

2.1 7种传播行为

Spring在 TransactionDefinition 中定义了7种传播行为:

传播行为 含义 常用场景
REQUIRED 支持当前事务,如果不存在则新建 默认值,绝大多数场景
SUPPORTS 支持当前事务,如果不存在则以非事务方式执行 查询方法,有无事务都可
MANDATORY 支持当前事务,如果不存在则抛异常 强制在事务中执行
REQUIRES_NEW 新建事务,如果存在则挂起当前事务 独立日志记录、异步操作
NOT_SUPPORTED 以非事务方式执行,如果存在则挂起 不需要事务的操作
NEVER 以非事务方式执行,如果存在则抛异常 禁止事务的操作
NESTED 如果存在事务则在嵌套事务中执行,否则等同于REQUIRED 部分回滚场景

2.2 核心传播行为源码分析

// TransactionAspectSupport.createTransactionIfNecessary()
protected TransactionInfo createTransactionIfNecessary(
        PlatformTransactionManager tm, TransactionAttribute txAttr, String joinpointIdentification) {

    // 如果未指定事务属性,返回null
    if (txAttr == null || tm == null) {
        return null;
    }

    TransactionStatus status = null;
    if (txAttr.getPropagationBehavior() != TransactionDefinition.PROPAGATION_SUPPORTS) {
        // 获取当前事务(通过TransactionSynchronizationManager)
        Object currentTransaction = tm.getTransaction(txAttr);
        status = tm.getTransaction(txAttr);
    } else {
        // SUPPORTS传播级别,没有事务则直接执行
    }

    // 包装事务信息
    return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

2.3 传播行为演示

@Service
public class PropagationService {

    @Autowired
    private PropagationService self;

    // 1. REQUIRED(默认)
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
        // 如果当前有事务,加入;否则新建
    }

    // 2. REQUIRES_NEW
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB() {
        // 总是新建事务,挂起当前事务
    }

    // 3. NESTED
    @Transactional(propagation = Propagation.NESTED)
    public void methodC() {
        // 嵌套事务,依赖于数据库的保存点
    }
}

// 测试场景
@Service
public class TransactionTestService {
    @Autowired
    private PropagationService propagationService;

    @Transactional(propagation = Propagation.REQUIRED)
    public void testRequired() {
        // 事务A开始
        try {
            propagationService.methodA(); // 加入A
            propagationService.methodB(); // 挂起A,创建新事务B
            // B提交后,恢复A
        } catch (Exception e) {
            // A可以回滚,但B已经提交
        }
    }
}

REQUIRED vs REQUIRES_NEW 对比

特性 REQUIRED REQUIRES_NEW
事务关系 加入已有事务 挂起已有事务,创建新事务
回滚影响 内部异常会导致外部回滚 内部回滚不影响外部
外部异常 内部会回滚 内部不会回滚
资源占用 共享连接 独立连接(挂起会释放连接)

三、事务隔离级别

3.1 数据库隔离级别回顾

隔离级别 脏读 不可重复读 幻读
READ_UNCOMMITTED
READ_COMMITTED
REPEATABLE_READ ✅(MySQL默认)
SERIALIZABLE

3.2 Spring支持的隔离级别

public enum Isolation {
    DEFAULT(-1),           // 使用数据库默认隔离级别
    READ_UNCOMMITTED(1),   // 读未提交
    READ_COMMITTED(2),     // 读已提交
    REPEATABLE_READ(4),    // 可重复读
    SERIALIZABLE(8);       // 串行化
}

3.3 隔离级别设置

@Transactional(isolation = Isolation.REPEATABLE_READ)
public void updateStock() {
    // 业务代码
}

四、事务失效的7种场景

4.1 场景一:非public方法

// ❌ 失效:private方法无法被代理
@Transactional
private void updateData() {
    // 事务不生效
}

// ❌ 失效:protected、default(package-private)同样失效
@Transactional
void updateData() {
    // 事务不生效(如果未配置proxy-target-class)
}

// ✅ 正确:public方法
@Transactional
public void updateData() {
    // 事务生效
}

原因:Spring默认使用JDK动态代理,只能代理public方法;即使使用CGLIB,也只能代理public和protected(但一般不推荐)。

4.2 场景二:自调用(内部调用)

@Service
public class UserService {

    @Transactional
    public void methodA() {
        // 外部调用:事务生效
        methodB();  // ❌ 内部调用,事务失效
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB() {
        // 期望:REQUIRES_NEW,实际:无事务
    }
}

解决方案

// 方案1:注入自身(通过代理调用)
@Service
public class UserService {
    @Autowired
    private UserService self;

    public void methodA() {
        self.methodB();  // ✅ 事务生效
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB() {}
}

// 方案2:使用AopContext.currentProxy()
public void methodA() {
    ((UserService) AopContext.currentProxy()).methodB();
}

4.3 场景三:异常类型不正确

// ❌ 默认只回滚RuntimeException和Error
@Transactional
public void method() {
    try {
        // 业务代码
    } catch (SQLException e) {  // SQLException是Exception,不是RuntimeException
        // 事务不会回滚
        throw e;  // 但抛出的是SQLException,也不会回滚
    }
}

// ✅ 指定回滚异常
@Transactional(rollbackFor = Exception.class)
public void method() {
    // 所有异常都会回滚
}

// ✅ 明确不回滚某些异常
@Transactional(noRollbackFor = IllegalArgumentException.class)
public void method() {
    // 抛出IllegalArgumentException不会回滚
}

4.4 场景四:多线程环境

@Service
public class AsyncService {

    @Transactional
    public void methodA() {
        new Thread(() -> {
            // ❌ 子线程中的事务不生效
            methodB();
        }).start();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB() {
        // 无事务
    }
}

原因:Spring事务管理器和数据库连接绑定到当前线程(ThreadLocal),子线程无法获取父线程的事务。

4.5 场景五:数据库引擎不支持事务

-- MySQL MyISAM引擎不支持事务,事务失效
CREATE TABLE user (
    id int,
    name varchar(100)
) ENGINE=MyISAM;

注意:确保你的表使用的是支持事务的引擎,例如 InnoDB。这是很多事务配置正确但依然不生效的常见“坑”之一,更多 数据库 相关知识可以深入探讨。

4.6 场景六:事务传播特性使用不当

// 外层无事务,内层REQUIRES_NEW不会创建新事务
public void outer() {  // 无事务
    inner();  // ❌ 不会创建新事务,因为外层没有事务
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void inner() {
    // 事务不生效
}

4.7 场景七:异常被捕获未抛出

@Transactional
public void method() {
    try {
        // 业务代码,可能抛出异常
    } catch (Exception e) {
        // ❌ 捕获异常后未重新抛出,事务不会回滚
        log.error("error", e);
    }
}

五、分布式事务解决方案

当微服务架构下,一个业务操作涉及多个数据源或服务时,就需要分布式事务。

5.1 分布式事务理论

CAP理论:分布式系统无法同时满足一致性(Consistency)、可用性(Availability)、分区容错性(Partition Tolerance),只能三选二。

BASE理论:基本可用(Basically Available)、软状态(Soft State)、最终一致性(Eventually Consistent)。

5.2 常见解决方案

方案 原理 适用场景 优点 缺点
2PC/3PC 两阶段提交协议 强一致性要求 实现简单 性能差,单点故障
TCC Try-Confirm-Cancel 强一致性、高并发 最终一致,性能好 侵入性大,实现复杂
可靠消息 消息事务+本地消息表 最终一致 解耦,高可用 有延迟
最大努力通知 异步通知+重试 对一致性要求不高 简单 不能保证强一致
Seata AT模式(自动补偿) 通用微服务 无侵入,支持多种模式 性能略低于TCC

5.3 Seata AT模式原理(主流方案)

Seata的AT模式通过 数据源代理 实现,无需修改业务代码:

全局事务(Global Transaction)
    ├── 分支事务1(Branch Transaction)
    ├── 分支事务2(Branch Transaction)
    └── 分支事务3(Branch Transaction)

执行流程

  1. 第一阶段(执行分支事务)
    • 业务SQL执行
    • 记录 undo_log(前置镜像和后置镜像)
    • 提交本地事务
  2. 第二阶段(全局提交/回滚)
    • 全局提交:异步清理undo_log
    • 全局回滚:根据undo_log生成反向SQL补偿

5.4 可靠消息最终一致性(本地消息表)

// 本地消息表示例
@Transactional
public void createOrder(Order order) {
    // 1. 保存订单
    orderDao.insert(order);

    // 2. 保存消息记录(状态=待发送)
    messageDao.insert(new Message(order.getId(), MessageStatus.PENDING));
}

// 定时任务扫描待发送消息
@Scheduled(cron = "0/10 * * * * ?")
public void sendMessage() {
    List<Message> messages = messageDao.selectPending();
    for (Message msg : messages) {
        try {
            // 发送MQ消息
            mqProducer.send(msg);
            // 更新状态为已发送
            messageDao.updateStatus(msg.getId(), MessageStatus.SENT);
        } catch (Exception e) {
            // 重试
        }
    }
}

六、实战案例

6.1 案例一:资金转账事务失效排查

现象:转账接口偶尔出现数据不一致,A账户扣款成功,B账户未入账。

排查过程

  1. 检查方法是否为public(✅)
  2. 检查异常是否被捕获(❌ 发现 SQLException 被捕获后未抛出)
  3. 检查事务传播行为(✅ REQUIRED)
  4. 检查数据库引擎(✅ InnoDB)

根因

catch (SQLException e) {
    log.error("转账失败", e);
    throw new RuntimeException("转账失败", e); // ✅ 解决方案:抛出运行时异常
}

6.2 案例二:嵌套事务回滚问题

需求:主方法记录操作日志,即使业务失败也要保存日志。

@Service
public class BusinessService {
    @Autowired
    private LogService logService;

    @Transactional
    public void doBusiness() {
        try {
            // 业务逻辑
            insertData();
        } catch (Exception e) {
            // 业务失败,需要回滚,但日志不能回滚
            logService.saveLog("业务失败");
            throw e;
        }
    }
}

@Service
public class LogService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveLog(String msg) {
        // 独立事务,不受外部影响
    }
}

6.3 案例三:分布式事务Seata实践

# application.yml
seata:
  enabled: true
  application-id: order-service
  tx-service-group: my_test_tx_group
  config:
    type: nacos
  registry:
    type: nacos
@GlobalTransactional
public void createOrder(Order order) {
    // 本地事务(订单库)
    orderDao.insert(order);

    // 远程调用库存服务(独立数据源)
    inventoryFeign.decrease(order.getProductId(), order.getQuantity());

    // 远程调用账户服务(独立数据源)
    accountFeign.decrease(order.getUserId(), order.getAmount());
}

七、思维导图总结

Spring事务机制
├── 核心原理
│   ├── PlatformTransactionManager
│   ├── TransactionInterceptor(AOP)
│   └── TransactionStatus
├── 传播行为(7种)
│   ├── REQUIRED(默认)
│   ├── REQUIRES_NEW
│   ├── NESTED
│   └── SUPPORTS / MANDATORY / NOT_SUPPORTED / NEVER
├── 隔离级别
│   ├── DEFAULT(数据库默认)
│   ├── READ_UNCOMMITTED / READ_COMMITTED
│   └── REPEATABLE_READ / SERIALIZABLE
├── 事务失效场景
│   ├── 非public方法
│   ├── 自调用(内部调用)
│   ├── 异常类型不匹配
│   ├── 多线程环境
│   ├── 数据库引擎不支持
│   ├── 传播特性使用不当
│   └── 异常被捕获未抛出
└── 分布式事务
    ├── 2PC/3PC
    ├── TCC
    ├── 可靠消息
    ├── 最大努力通知
    └── Seata(AT模式)

八、面试Tips

加分话术

  1. 源码结合:“Spring事务通过 TransactionInterceptor 拦截目标方法,根据 @Transactional 配置的事务属性,由 PlatformTransactionManager 管理事务。”
  2. 失效分析:“自调用失效是因为Spring的AOP基于代理,内部调用走的是 this 而非代理对象,无法触发事务增强。”
  3. 传播行为:“REQUIRES_NEW 会挂起当前事务,开启新事务,常用于日志记录等独立操作,避免主事务回滚导致日志也回滚。”
  4. 分布式事务:“Seata的AT模式通过数据源代理记录 undo_log ,实现自动补偿,对业务代码无侵入,适合微服务架构。”

常见追问准备

  • Spring事务的传播行为在嵌套事务中如何工作?
  • @Transactional 注解的 readOnly 属性有什么用?
  • 什么是“事务挂起”?如何实现?
  • 如何自定义事务管理器?
  • 分布式事务的最终一致性如何保证?

避坑指南

  • ❌ 不要混淆 Propagation.NESTEDREQUIRES_NEW——嵌套事务依赖于保存点,外部回滚内部也回滚
  • ❌ 不要认为所有异常都会回滚——默认只回滚 RuntimeExceptionError
  • ❌ 不要忽视 @Transactional 在同一个类中调用失效的问题
  • ✅ 强调“使用 self 注入或 AopContext.currentProxy() 解决自调用”
  • ✅ 区分“声明式事务”和“编程式事务”

理解Spring事务的这些核心机制,不仅能从容应对 面试求职 中的挑战,更是编写健壮、可靠业务代码的基石。希望这篇深入浅出的解析能帮助大家,也欢迎在 云栈社区Java 版块交流更多实战经验和疑难杂症。




上一篇:QoderWork桌面智能助手体验:本地AI真能替你干活了
下一篇:Azure AKS实战:从集群部署到AGIC网关集成与HTTPS流量路由
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-30 04:56 , Processed in 0.583768 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表