一文详解Spring 事务失效场景

前提:

Spring 事务底层依靠 AOP 动态代理,只有代理对象调用方法才会触发事务增强;

同时事务生效需要满足数据库引擎支持事务(InnoDB,MyISAM 不支持)。

注解:@Transactional

1. 方法不是 public(最常见)

Spring AOP 只拦截 public 方法,private/protected/default 方法加注解完全无效。

java 复制代码
@Service
public class UserService {
    // ❌ private,事务失效
    @Transactional
    private void addUser() {
        userMapper.insert(...);
        int i = 1/0; // 抛异常不会回滚
    }
}

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

在同一个类中,直接 this.xxx() 调用带 @Transactional 的方法,不是代理对象调用,AOP 不会切入。

java 复制代码
@Service
public class UserService {

    public void test() {
        // ❌ this调用,不是代理对象,addUser事务失效
        this.addUser();
    }

    @Transactional
    public void addUser() {
        userMapper.insert(...);
        int i = 1/0; // 异常不回滚
    }
}

✅ 解决方案:

自身注入 UserService self;,self.addUser()。

3. 异常类型不对:捕获了异常 / 抛出非回滚异常

@Transactional 默认只对 RuntimeException 和 Error 触发回滚;

受检异常(Exception)默认不回滚。

场景 3.1 自己 try-catch 吃掉异常

java 复制代码
@Transactional
public void addUser() {
    try {
        userMapper.insert(...);
        int i = 1/0;
    } catch (Exception e) {
        // ❌ 异常被捕获,没有抛出给AOP,事务不会回滚
        log.error("error", e);
    }
}

场景 3.2 抛出受检异常,没指定 rollbackFor

java 复制代码
@Transactional
public void addUser() throws IOException {
    userMapper.insert(...);
    throw new IOException("IO异常");
    // ❌ IOException是受检异常,默认不回滚
}

✅ 修复:@Transactional(rollbackFor = Exception.class)

4. 传播行为配置错误

典型:

REQUIRES_NEW 独立事务;

SUPPORTS 非事务环境下就是无事务;

NOT_SUPPORTED 强制以非事务运行。

java 复制代码
// ❌ NOT_SUPPORTED:不开启事务,发生异常不会回滚
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void addUser(){
    userMapper.insert(...);
    int i=1/0;
}

5. 数据库底层不支持事务

MyISAM 引擎不支持事务,哪怕代码写对 @Transactional,也无法回滚。

查看表引擎:show create table user; ✅ 必须使用 InnoDB。

6. 多线程场景,事务失效

事务绑定在当前线程,新开子线程执行数据库操作,子线程是全新事务上下文,和主线程事务隔离。

java 复制代码
@Transactional
public void test() {
    userMapper.insertA();
    new Thread(() -> {
        userMapper.insertB();
        int i=1/0; // ❌ 子线程异常,不会回滚insertA
    }).start();
}

7. 事务超时时间,超时自动回滚(不是不生效,是触发回滚)

java 复制代码
// 5秒超时,超时后自动回滚
@Transactional(timeout = 5)
public void test() throws InterruptedException {
    userMapper.insert();
    Thread.sleep(6000);
}

8. 注解被错误放在接口上(老版本 Spring)

Spring AOP 优先读取实现类上的注解;

只在接口上加 @Transactional,使用 CGLIB 代理时事务会失效。

最佳实践:@Transactional 写在 Service 实现类的 public 方法。

9. 嵌套事务,传播 NESTED,外层捕获异常导致内层不回滚

java 复制代码
@Service
public class UserService {
    @Autowired
    private OrderService orderService;

    @Transactional
    public void main() {
        userMapper.insertUser();
        try {
            // NESTED 嵌套事务
            orderService.addOrder();
        } catch (Exception e) {
            log.error("子事务异常");
            // ❌ 外层捕获异常,外层事务不回滚;内层子事务回滚
        }
    }
}
@Service
class OrderService{
    @Transactional(propagation = Propagation.NESTED)
    public void addOrder(){
        orderMapper.insert();
        int i=1/0;
    }
}

总结

  1. AOP 代理问题:非 public、同类 this 调用
  2. 异常问题:吞异常、rollbackFor 没配置
  3. 数据库:MyISAM 引擎
  4. 传播属性:NOT_SUPPORTED/SUPPORTS 误用
  5. 线程:跨线程事务隔离
  6. 注解位置:仅接口上加注解(CGLIB)

扩展

Q:为什么同类 this 调用事务失效?

A:Spring 事务由 AOP 代理对象增强;this 是原始对象,没有经过代理,不会执行事务拦截器。
Q:rollbackFor=Exception.class 作用?

A:默认只回滚 RuntimeException/Error;加上之后所有 Exception(包含受检异常)都会触发回滚。

Spring 事务传播行为(Propagation)

核心:多个事务方法相互调用时,事务如何传递,一共 7 种,

传播类型 含义 场景说明
REQUIRED(默认) 如果当前存在事务,就加入该事务;没有则新建一个事务 绝大多数业务,同一件事,要么全部成功要么全部回滚
REQUIRES_NEW 新建独立事务;如果当前有事务,挂起原有事务,执行新事务,互不影响 日志记录、独立审计,子事务失败不影响外层事务
SUPPORTS 当前有事务就加入;没有事务就以非事务方式执行 查询场景,可有可无事务
MANDATORY 必须运行在已有事务中;当前无事务直接抛异常 强制外层必须开启事务,防止单独调用
NOT_SUPPORTED 以非事务方式执行;当前有事务就挂起原有事务 大批量查询,不想占用事务资源
NEVER 强制非事务运行;如果当前存在事务,直接抛异常 禁止事务环境,只读统计
NESTED 嵌套事务:有事务则嵌套在当前事务内(保存点 savepoint);无事务新建事务 子事务回滚不会导致外层事务回滚;外层事务回滚,嵌套子事务一定回滚

重点区分

REQUIRES_NEW vs NESTED

  1. REQUIRES_NEW:两个完全独立事务,外层事务挂起。子事务提交后不受外层影响;子事务异常外层可以捕获继续提交。
  2. NESTED :同一个物理事务,基于保存点 savepoint。只有外层事务提交,嵌套事务才真正提交;外层回滚,嵌套一定回滚。

NESTED 不是独立事务,是保存点;

REQUIRES_NEW 是全新独立事务。

扩展

  1. 传播行为默认值?REQUIRED
  2. REQUIRES_NEW 挂起事务原理?Spring 事务管理器暂停当前事务,保存上下文,执行完恢复
  3. 哪些传播行为会创建新事务:REQUIRED、REQUIRES_NEW、NESTED

Spring 7 种事务传播行为 + 代码演示 + 回滚规则

前提:

  1. AOP 代理调用才生效;同类方法内部调用事务失效。
  2. @Transactional 默认只对 RuntimeException / Error 触发回滚,受检异常默认不回滚。
  3. 外层方法叫内层方法,外层 = 调用方,内层 = 被调用方。

公共基础代码(两个 Service,方便测试)

java 复制代码
@Service
public class OuterService {
    @Autowired
    private InnerService innerService;

    // 外层方法
    public void outer(){}
}

@Service
public class InnerService {
    // 内层方法
    public void inner(){}
}

1. REQUIRED(默认)

有事务就加入当前事务;无事务新建事务。

外层内层是同一个事务

java 复制代码
@Service
public class OuterService {
    @Autowired
    private InnerService innerService;

    @Transactional(propagation = Propagation.REQUIRED)
    public void outer() {
        // 插入订单
        innerService.inner();
        // 外层抛异常
        throw new RuntimeException("外层异常");
    }
}

@Service
public class InnerService {
    @Transactional(propagation = Propagation.REQUIRED)
    public void inner() {
        // 扣库存
    }
}

✅ 回滚规则:

  • 外层抛异常 → 整个事务回滚,内层操作回滚
  • 内层抛异常,外层不捕获 → 整个事务回滚
  • 内层抛异常,外层 try-catch 接住:依然会回滚! 异常标记了事务为 rollback-only,外层 catch 后提交时报异常。

场景:正常业务,增删改,同业务要么全成要么全回。


2. REQUIRES_NEW

如果外层有事务,挂起外层事务,新建独立事务。两个完全独立物理事务,互不干扰。

java 复制代码
@Service
public class OuterService {
    @Autowired
    private InnerService innerService;

    @Transactional
    public void outer() {
        // 插入订单
        try {
            innerService.inner();
        } catch (Exception e) {
            // 捕获内层异常,外层继续跑
        }
        // 外层正常结束,外层事务提交
    }
}

@Service
public class InnerService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void inner() {
        // 扣库存
        throw new RuntimeException("内层异常");
    }
}

✅ 回滚规则:

  1. 内层抛异常 → 内层事务回滚;外层事务不受影响,可以正常提交(try-catch 捕获内层异常)
  2. 外层抛异常 → 外层事务回滚;内层事务已经独立提交完成,不会回滚

重点:内层事务执行完就提交,外层事务被挂起,两者独立。 适用:操作日志、审计记录,就算主业务失败,日志也要入库。


3. SUPPORTS

当前有事务,就加入事务;没有事务,就非事务方式运行

java 复制代码
@Service
public class OuterService {
    @Autowired
    private InnerService innerService;

    @Transactional
    public void outer() {
        // 外层存在事务
        innerService.inner();
    }
}

@Service
public class InnerService {
    @Transactional(propagation = Propagation.SUPPORTS)
    public void inner() {
        // 查询或者更新
    }
}

✅ 回滚规则:

  • 外层有事务:内层加入外层事务,任意地方异常,整体回滚(和 REQUIRED 行为一致)
  • 外层没有事务:内层无事务,发生异常不会回滚,已经执行的 SQL 持久化

适用:查询方法,有事务环境就参与事务,没有就普通查询。


4. MANDATORY

必须运行在已有事务中;外层没有事务直接抛异常,不会新建事务

java 复制代码
@Service
public class OuterService {
    @Autowired
    private InnerService innerService;

    // 外层不加 @Transactional
    public void outer() {
        innerService.inner(); // ❌ 直接抛出 IllegalTransactionStateException
    }
}

@Service
public class InnerService {
    @Transactional(propagation = Propagation.MANDATORY)
    public void inner() {
    }
}

✅ 回滚规则:

  • 外层存在事务:内层加入外层事务,统一回滚;
  • 外层无事务:直接报错,方法不执行。

适用:强制该方法必须被事务方法调用,防止单独调用造成数据不一致。


5. NOT_SUPPORTED

以非事务方式执行;外层有事务就挂起外层事务,内层不在事务里
挂起(suspend) ≠ 提交 / 回滚外层事务

只是临时把外层事务暂停、放到一边保存起来,然后执行当前这个无事务的方法;

等内层方法执行完毕之后,再恢复外层事务继续跑。

概括:

外层事务先暂停,内层裸跑(没有事务);内层跑完,再把外层事务捡回来继续执行。

java 复制代码
@Service
public class OuterService {
    @Autowired
    private InnerService innerService;

    @Transactional
    public void outer() {
        // 外层事务开启,调用内层时外层事务被挂起
        innerService.inner();
        // 内层执行完,恢复外层事务
    }
}

@Service
public class InnerService {
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void inner() {
        // 内层无事务,这里抛异常,内层SQL不会回滚
        throw new RuntimeException();
    }
}

✅ 回滚规则:

  • 内层代码抛异常:内层操作不会回滚(无事务);外层捕获异常后,外层事务继续提交;外层没捕获,外层事务回滚。

适用:大批量查询,大报表,不想占用事务连接。


6. NEVER

禁止在事务中执行;外层存在事务直接抛异常,必须非事务环境

java 复制代码
@Service
public class OuterService {
    @Autowired
    private InnerService innerService;

    @Transactional
    public void outer() {
        innerService.inner(); // ❌ 抛出 IllegalTransactionStateException
    }
}

@Service
public class InnerService {
    @Transactional(propagation = Propagation.NEVER)
    public void inner() {
    }
}

✅ 回滚规则:

  • 外层有事务:直接报错;
  • 外层无事务:普通非事务执行,异常不会回滚。

适用:统计任务,明确禁止事务环境。


7. NESTED 嵌套事务

外层有事务:基于 JDBC Savepoint 保存点,嵌套在同一个物理事务;外层无事务新建事务。 ⚠️ 和 REQUIRES_NEW 最大区别:同一个物理事务,不是独立事务

java 复制代码
@Service
public class OuterService {
    @Autowired
    private InnerService innerService;

    @Transactional
    public void outer() {
        // 外层事务开启,创建savepoint
        try {
            innerService.inner();
        } catch (Exception e) {
            // 捕获内层异常,只回滚到savepoint,外层事务还能继续提交
            System.out.println("内层异常,但是外层可以提交");
        }
        // 外层正常执行,最终提交
    }
}

@Service
public class InnerService {
    @Transactional(propagation = Propagation.NESTED)
    public void inner() {
        throw new RuntimeException("内层嵌套异常");
    }
}

✅ 回滚规则:

  1. 内层抛异常,外层 try-catch 捕获:只回滚内层操作,外层事务可以正常提交 ✅
  2. 外层抛异常 :无论内层是否成功,外层整体回滚,内层 savepoint 一起回滚

底层:

JDBC Savepoint,不是独立事务。MySQL InnoDB 支持。

适用:

主流程不能回滚,但子步骤失败可以单独撤销子步骤。


🔥 核心对比总结表

传播行为 外层有事务 外层无事务 独立事务 内层异常 (外层 catch) 外层异常
REQUIRED 加入外层事务 新建事务 否 标记 rollback-only,整体回滚 整体回滚
REQUIRES_NEW 挂起外层,新建独立事务 新建事务 是 内层回滚,外层可提交 外层回滚,内层已提交不受影响
SUPPORTS 加入外层事务 非事务执行 否 整体回滚 整体回滚
MANDATORY 加入外层事务 抛异常 否 整体回滚 整体回滚
NOT_SUPPORTED 挂起外层,内层非事务 非事务执行 否 内层不回滚 外层回滚,内层已持久化
NEVER 抛异常 非事务执行 否 无机会执行 无机会执行
NESTED 创建 savepoint 嵌套 新建事务 否 仅回滚 savepoint,外层可提交 外层整体回滚
相关推荐
Q26433650231 小时前
【有源码】基于Spring Boot和Vue的在线考试与交流系统-教育信息化背景下在线考试与交流一体化平台
java·vue.js·spring boot·mysql·spring·毕业设计·课程设计
Mikko71 小时前
JVM 线上排查实战(七):jps 看不到进程、jstack 报不允许的操作怎么办?attach 失败的六种情况实测
java·运维·jvm·后端
jianjinwssy1 小时前
MyBatis-Plus 从入门到实战:常用功能与核心原理
java·mybatis
会编程的吕洞宾1 小时前
Spring AI 2.0 会话记忆实战,让 AI 助手记住每一次对话
java·人工智能·spring
小宋10211 小时前
Agent 工具升级如何不破坏线上:Tool Schema 版本兼容与契约测试
java·人工智能·后端·spring
汤米粥1 小时前
后端开发主流技术方案
java·python·golang·php·nodejs·后端开发
Helix2501 小时前
2026编程入门指南:零基础如何科学选择第一门编程语言?
java·c语言·javascript·零基础·编程语言·python入门·编程就业
EatFan1 小时前
2026 后端 AI 集成三条落地路径:Spring AI 2.0、MCP 协议与旁路服务怎么选
java·人工智能·spring boot·spring·大模型·spring ai·mcp
SimonKing1 小时前
QClaw关停之后:一个工具的退场,一段关系的告别
java·后端·程序员