【高频面试题】spring事物失效场景(带原理 + 代码示例)

目录

[1. 方法不是 public(最基础)](#1. 方法不是 public(最基础))

现象

原理

[2. 同类内方法调用(this 调用,不走代理,最高频)](#2. 同类内方法调用(this 调用,不走代理,最高频))

现象

原理

[3. 异常类型不对:捕获了异常 / 抛出受检异常,默认不回滚](#3. 异常类型不对:捕获了异常 / 抛出受检异常,默认不回滚)

[场景 3.1 try-catch 捕获异常,没有抛出](#场景 3.1 try-catch 捕获异常,没有抛出)

[场景 3.2 抛出受检异常(Exception),没有指定 rollbackFor](#场景 3.2 抛出受检异常(Exception),没有指定 rollbackFor)

[4. 数据库引擎不支持事务](#4. 数据库引擎不支持事务)

[5. 传播行为配置错误](#5. 传播行为配置错误)

[6. 多线程场景事务失效](#6. 多线程场景事务失效)

原理

[7. 注解加在类上,但方法是 final/static](#7. 注解加在类上,但方法是 final/static)

[8. 事务超时、只读配置误用](#8. 事务超时、只读配置误用)

[9. 嵌套事务场景:内部异常被 catch,外部事务不回滚(传播机制)](#9. 嵌套事务场景:内部异常被 catch,外部事务不回滚(传播机制))

面试精简总结(口述版)

面试官高频追问

[Q1:@Transactional(rollbackFor = Exception.class) 为什么要写?](#Q1:@Transactional(rollbackFor = Exception.class) 为什么要写?)

[Q2:什么是 rollback-only?](#Q2:什么是 rollback-only?)

[Q3:Spring 事务底层用什么保存事务信息?](#Q3:Spring 事务底层用什么保存事务信息?)

[Q4:两种代理 JDK 动态代理 vs CGLIB](#Q4:两种代理 JDK 动态代理 vs CGLIB)

面试真题

题目

参考答案


Spring 事务底层是AOP 动态代理。只有被代理对象调用 public 方法,并且通过代理对象执行方法,事务才生效。

核心一句话:事务是代理对象增强出来的能力;如果不走代理,事务直接失效。

1. 方法不是 public(最基础)

现象

@Transactional 加在 private /protected/default 方法上,事务不生效。

原理

Spring AOP 只能拦截 public 方法;非 public 方法不会生成代理增强逻辑。

java 复制代码
// ❌ 失效
@Transactional
private void testTx(){
    userMapper.insert(...);
    int i = 1/0;
}

✅ 解决:改成 public。

2. 同类内方法调用(this 调用,不走代理,最高频)

现象

同一个类中,无事务的方法 A,直接调用本类带 @Transactional 的方法 B,事务失效。

java 复制代码
@Service
public class UserService {
    public void A() {
        // this是原始对象,不是代理对象!!
        this.B(); 
    }
    @Transactional
    public void B() {
        userMapper.insert(...);
        int i = 1/0; // 抛异常,不会回滚!
    }
}

原理

this.B() 调用的是原始对象的方法,不是 Spring 生成的代理对象。

代理对象的事务增强逻辑没有执行。

✅ 解决:

  1. 自己注入自己 @Autowired private UserService self;,用 self.B()
  2. 使用 AopContext.currentProxy () 获取代理对象(需要开启 exposeProxy=true
  3. 拆分成两个类

3. 异常类型不对

捕获了异常 / 抛出受检异常,默认不回滚

Spring 事务默认只对 RuntimeException 和 Error 回滚

无 rollbackFor:只认RuntimeExceptionError

场景 3.1 try-catch 捕获异常,没有抛出

java 复制代码
@Transactional
public void test(){
    try{
        userMapper.insert(...);
        int i=1/0;
    }catch (Exception e){
        // 吃掉异常,代理感知不到异常,不会触发回滚
        log.error("err",e);
    }
}

✅ 解决:catch 之后重新抛出异常 throw e;

java 复制代码
@Transactional
public void test(){
    try{
        userMapper.insert(...);
        int i=1/0;
    }catch (Exception e){
        log.error("err",e);
        // 重新抛出异常,让AOP代理捕获到,触发事务回滚
        throw e;
        //优化:或者包装为运行时异常抛出
        //throw new RuntimeException("业务操作失败", e);
    }
}

补充说明

  1. 原理:@Transactional 的增强逻辑是在方法执行完毕后判断是否抛出异常;只有方法抛出异常,Spring 才会标记事务回滚。catch 住不抛,代理认为方法正常执行,直接提交事务。
  2. 优化写法(很多业务不会直接 throw 原始 e,包装自定义异常):

重要面试小坑

如果抛出的是受检异常(Exception) ,即使 throw e,默认也不会回滚

示例(❌ 这样写依然不回滚):

java 复制代码
@Transactional
public void test() throws Exception{
    try{
        userMapper.insert(...);
        throw new Exception("受检异常");
    }catch (Exception e){
        log.error("err",e);
        throw e; // Exception是受检异常,Spring默认不回滚
    }
}

✅ 需要指定 rollbackFor

rollbackFor=Exception.class:只要抛 Exception 及其子类就回滚。

java 复制代码
@Transactional(rollbackFor = Exception.class)
public void test() throws Exception{
    try{
        userMapper.insert(...);
        throw new Exception("受检异常");
    }catch (Exception e){
        log.error("err",e);
        throw e;
    }
}

场景 3.2 抛出受检异常(Exception),没有指定 rollbackFor

java 复制代码
// ❌ 默认不会回滚!
@Transactional
public void test() throws Exception{
    userMapper.insert(...);
    throw new Exception("受检异常");
}

✅ 解决:指定回滚类型

java 复制代码
@Transactional(rollbackFor = Exception.class)

面试一句话总结

  1. 异常必须抛给 AOP 代理,才会进入回滚判断;
  2. 无 rollbackFor:只认RuntimeExceptionError
  3. rollbackFor=Exception.class:只要抛 Exception 及其子类就回滚。

4. 数据库引擎不支持事务

比如 MySQL 使用 MyISAM 引擎,MyISAM 不支持事务,无论怎么加注解都没用。

✅ 解决:改成 InnoDB。

5. 传播行为配置错误

举典型:REQUIRES_NEWSUPPORTSNOT_SUPPORTED 例:@Transactional(propagation = Propagation.NOT_SUPPORTED):以非事务方式运行; 或者 REQUIRES_NEW 嵌套事务,内部异常被捕获,外部不会回滚。

面试常问:REQUIRED(默认):有事务就加入,没有新建事务。

6. 多线程场景事务失效

java 复制代码
@Transactional
public void test(){
    new Thread(()->{
        userMapper.insert(...);
        int i=1/0;
    }).start();
}

原理

Spring 事务绑定在当前线程 ThreadLocal。新开线程是全新 Thread,事务上下文不会传递,子线程操作属于独立事务。 子线程抛异常,不会回滚主线程的事务。

7. 注解加在类上,但方法是 final/static

  • final 方法:无法被子类重写,CGLIB 无法增强,事务失效
  • static 方法:静态属于类,不属于实例,代理无法增强
java 复制代码
@Transactional
public final void test(){ ... } // ❌失效

8. 事务超时、只读配置误用

@Transactional(readOnly = true):只读事务,不能执行增删改,写操作会抛异常。

9. 嵌套事务场景

内部异常被 catch,外部事务不回滚(传播机制)

java 复制代码
@Transactional
public void outer() {
    try {
        inner();
    } catch (Exception e) {
    }
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void inner(){
    throw new RuntimeException();
}

inner 是独立事务,inner 回滚;outer 捕获异常,outer 事务正常提交。

如果是默认 REQUIRED,内部抛异常,标记事务回滚点,即使外层 catch,提交时依然会报:

Transaction rolled back because it has been marked as rollback-only

精简总结

Spring 事务基于 AOP 代理,事务失效主要原因:

  1. 方法非 public;final/static 方法无法代理
  2. 同类内 this 调用,不走代理对象
  3. 异常被 try-catch 吃掉,或者抛出受检异常,没配置 rollbackFor
  4. 数据库引擎不支持事务(MyISAM)
  5. 多线程:事务存 ThreadLocal,子线程无法继承事务上下文
  6. 事务传播行为配置错误
  7. readOnly=true 执行写操作

面试官高频追问

Q1:@Transactional(rollbackFor = Exception.class) 为什么要写?

Spring 事务默认只对 RuntimeException、Error 回滚。

Exception 包含受检异常,如果业务抛出 IOException 等受检异常,不加 rollbackFor 不会回滚。

Q2:什么是 rollback-only?

rollback-only 是事务的一个标记。

一旦这个标记被打上,代表这个事务只能回滚,不能提交。

哪怕外层代码捕获了异常,最后提交的时候 Spring 依然强制回滚,并且抛出异常:Transaction rolled back because it has been marked as rollback-only。

rollback-only 标记,只有在「发生了满足回滚规则的异常」的时候才会被打上。不会凭空出现。

打上标记之后:不管后续代码有没有异常、代码是否正常跑完,这个事务都禁止提交,最终一定会回滚。

不是 "事务没出错也会回滚";而是曾经出过一次触发回滚的异常,给事务打上标记,就算后面代码正常,也不能提交了
前提:同一个事务(传播行为 REQUIRED,默认)

场景演示(最经典)

java 复制代码
@Service
public class TxDemoService {

    // 外层方法
    @Transactional
    public void outer() {
        try {
            inner();
        } catch (Exception e) {
            log.error("捕获内层异常", e);
            // 注意:这里catch住了,没有向外抛!
        }
    }

    // 内层方法,REQUIRED,会加入外层的同一个事务
    @Transactional
    public void inner() {
        // 抛出运行时异常
        throw new RuntimeException("内层业务异常");
    }
}

执行流程拆解

  1. 调用 outer(),开启事务。
  2. outer 调用 inner();inner 是 REQUIRED,加入当前事务,不会新建事务
  3. inner 抛出 RuntimeException,Spring 捕获异常,给当前整个事务打上 rollback-only 标记
  4. 异常回到 outer,被 try-catch 捕获,没有继续向外抛出
  5. outer 方法执行完毕,准备提交事务。
  6. Spring 检查事务状态:发现有 rollback-only 标记。拒绝提交,强制回滚,并且抛出 Transaction rolled back because it has been marked as rollback-only

核心关键点

  1. 虽然外层 catch 吃掉异常,方法正常返回,但是事务标记已经不可逆
  2. rollback-only 是绑定在事务上的标记,不是绑定在方法上。同一个事务内任何一个地方抛出触发回滚的异常,整个事务就被标记。
  3. 这个标记一旦打上,不能清除,只能回滚。

❗ 容易踩坑区分:REQUIRES_NEW 不会出现 rollback-only

java 复制代码
@Transactional
public void outer() {
    try {
        inner();
    } catch (Exception e) {
        log.error("捕获内层异常", e);
    }
}

// REQUIRES_NEW 新建独立事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void inner() {
    throw new RuntimeException("异常");
}

流程:

  1. outer 开启事务
  2. inner 触发 REQUIRES_NEW:挂起外层事务,新建独立事务
  3. inner 抛异常 → inner 自己的事务回滚。
  4. inner 异常抛到 outer,被 catch。内层事务已经结束,不会给外层事务打 rollback-only 标记
  5. outer 继续执行,最后正常提交。

✅ 内外是两个独立事务,互不影响,不会 rollback-only。

追问:什么时候会打上 rollback-only?

同一个事务里面,方法抛出满足回滚条件的异常(默认 RuntimeException/Error,或者 rollbackFor 指定的异常),Spring 就会给当前事务打上 rollback-only。

面试题回答模板

rollback-only 是 Spring 事务的标记。当多个方法使用 REQUIRED 共用同一个事务,其中一个方法抛出回滚异常,Spring 会给整个事务打上 rollback-only 标记。

即使上层方法捕获异常不向外抛出,事务也不能提交,最终强制回滚,并抛出Transaction rolled back because it has been marked as rollback-only

本质:同一个事务,一损俱损

怎么避免 rollback-only 异常?

两种方案:

  1. 如果希望内层异常不影响外层提交:内层使用 REQUIRES_NEW,新开独立事务(注意:会挂起事务,有性能开销)。
  2. 业务上,如果内层异常不需要回滚整个事务:不要在内层抛出会触发回滚的异常,改用业务返回码,不抛异常。

Q3:Spring 事务底层用什么保存事务信息?

ThreadLocal,把事务状态、连接信息绑定到当前线程。所以多线程无法共享事务。

Q4:两种代理 JDK 动态代理 vs CGLIB

  • JDK 代理:目标必须实现接口,代理接口方法
  • CGLIB:继承目标类,重写方法;类不能是 final,方法不能 final

面试真题

题目

下面代码事务会不会生效?为什么?

java 复制代码
@Service
public class OrderService {
    public void createOrder() {
        saveOrder();
    }
    @Transactional
    public void saveOrder() {
        orderMapper.insert();
        int a = 1/0;
    }
}

参考答案

事务失效

原因:createOrder 通过 this 调用本类的 saveOrder,this 是原始对象,不是 Spring AOP 代理对象,事务增强逻辑不会执行,异常不会回滚。

相关推荐
小孩玩什么1 小时前
深入理解字符串匹配算法:BF算法,KMP算法
java·c语言·开发语言·数据结构·c++·算法
JohnCarter20211 小时前
双卡 Atlas 300I Duo 部署 Qwen3-VL-32B:血泪实录
后端
长谷深风1111 小时前
Agent执行系统中的身份与版本设计
java·大数据·人工智能·ai·大模型·task·aiagent
Wang's Blog2 小时前
Java框架快速入门: Spring Security+OAuth2之UserDetails与JDBC认证实践
java·网络·spring
爱从未走散2 小时前
面试八股题
java·面试·职场和发展
艺杯羹2 小时前
AI编程时代软件工程怎么学:从底层思维认知到驱动智能体的架构跃迁
java·人工智能·ai·架构·软件工程·ai编程
土司大王2 小时前
LeetCode hot100——51.N 皇后:Java 回溯模板、列与对角线剪枝、O(n!) 复杂度分析
java·leetcode·剪枝
码外生活2 小时前
🎥 手搓一套直播高并发环境!一个后端小白从 0 到 1 的搭建笔记
后端·spring cloud
重生之小比特2 小时前
【Java SE】IDEA 调试 Debug 案例完整分析
java·ide·intellij-idea