两个事务间的传播机制

两个事务间的传播机制

  • 两个事务间的传播机制
    • [一、前置知识:Spring 事务原理](#一、前置知识:Spring 事务原理)
    • 二、示例环境准备
    • [示例1:直接调用 public 方法(无事务注解)](#示例1:直接调用 public 方法(无事务注解))
    • [示例2:直接调用 private 方法](#示例2:直接调用 private 方法)
    • 示例3:直接调用带事务注解的方法(注解失效)
    • [示例4:通过代理调用 + REQUIRED(默认)](#示例4:通过代理调用 + REQUIRED(默认))
    • [示例5:REQUIRES_NEW + 代理调用](#示例5:REQUIRES_NEW + 代理调用)
    • [示例6:NESTED + 代理调用](#示例6:NESTED + 代理调用)
    • [示例7:捕获异常 + 直接调用(无代理)](#示例7:捕获异常 + 直接调用(无代理))
    • [示例8:REQUIRES_NEW + 直接调用(注解失效)](#示例8:REQUIRES_NEW + 直接调用(注解失效))
    • 三、总结对比表
    • 四、核心结论
    • 五、最佳实践建议

两个事务间的传播机制

核心问题:在同一个类中,一个带事务的方法直接调用另一个方法(无论是否有 @Transactional),事务注解是否生效?

答案:不生效,因为 Spring 事务基于代理,直接调用绕过了代理。


一、前置知识:Spring 事务原理

  • Spring 通过 AOP 动态代理 实现事务管理。
  • 只有通过 代理对象 调用方法,@Transactional 才会被拦截并生效。
  • this.method() 或直接调用 method() 不会经过代理,因此事务注解失效。

二、示例环境准备

java 复制代码
@RestController
public class TestController {

    @Autowired
    private UserService userService;
    @Autowired
    private PostService postService;

    // 获取代理对象的方法(方式之一)
    private TestController getProxy() {
        return (TestController) AopContext.currentProxy();
    }
}

示例1:直接调用 public 方法(无事务注解)

java 复制代码
@GetMapping("/test1")
@Transactional
public String test1() {
    userService.removeById(666L);
    removePost();
    return "ok";
}

public void removePost() {
    postService.removeById(666L);
    int a = 1 / 0; // 模拟异常
}

结果: ✅ 都回滚
原因: 异常传播到外层 test1(),外层事务回滚。

removePost() 虽然无事务,但在外层事务上下文中执行,异常导致外层回滚。


示例2:直接调用 private 方法

java 复制代码
@GetMapping("/test2")
@Transactional
public String test2() {
    userService.removeById(666L);
    removePost();
    return "ok";
}

private void removePost() {
    postService.removeById(666L);
    int a = 1 / 0;
}

结果: ✅ 都回滚
原因: 同上,private 方法不影响事务传播。


示例3:直接调用带事务注解的方法(注解失效)

java 复制代码
@GetMapping("/test3")
@Transactional
public String test3() {
    userService.removeById(666L);
    removePost(); // 直接调用
    return "ok";
}

@Transactional
public void removePost() {
    postService.removeById(666L);
    int a = 1 / 0;
}

结果: ✅ 都回滚
原因:

  • removePost()@Transactional 失效(直接调用)
  • 代码仍在事务A中执行
  • 异常导致事务A回滚

示例4:通过代理调用 + REQUIRED(默认)

java 复制代码
@GetMapping("/test4")
@Transactional
public String test4() {
    userService.removeById(666L);
    getProxy().removePost();
    return "ok";
}

@Transactional
public void removePost() {
    postService.removeById(666L);
    int a = 1 / 0;
}

结果: ✅ 都回滚
原因:

  • 代理调用使 @Transactional 生效
  • 传播行为 = REQUIRED,复用外层事务
  • 异常导致唯一事务回滚

示例5:REQUIRES_NEW + 代理调用

java 复制代码
@GetMapping("/test5")
@Transactional
public String test5() {
    userService.removeById(666L);
    try {
        getProxy().removePostNew();
    } catch (Exception e) {
        System.out.println("捕获异常");
    }
    return "ok";
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void removePostNew() {
    postService.removeById(666L);
    int a = 1 / 0;
}

结果:

  • userService.removeById(666L) ✅ 成功(外层事务提交)
  • postService.removeById(666L) ❌ 失败(独立事务回滚)
    原因:
  • REQUIRES_NEW 挂起外层事务,创建独立事务
  • 独立事务回滚,不影响外层事务

示例6:NESTED + 代理调用

java 复制代码
@GetMapping("/test6")
@Transactional
public String test6() {
    userService.removeById(666L);
    try {
        getProxy().removePostNested();
    } catch (Exception e) {
        System.out.println("捕获异常");
    }
    return "ok";
}

@Transactional(propagation = Propagation.NESTED)
public void removePostNested() {
    postService.removeById(666L);
    int a = 1 / 0;
}

结果:

  • userService.removeById(666L) ✅ 成功
  • postService.removeById(666L) ❌ 失败
    原因:
  • NESTED 创建保存点,内层回滚到保存点
  • 外层事务可以继续提交

示例7:捕获异常 + 直接调用(无代理)

java 复制代码
@GetMapping("/test7")
@Transactional
public String test7() {
    userService.removeById(666L);
    try {
        removePost(); // 直接调用
    } catch (Exception e) {
        System.out.println("捕获异常");
    }
    return "ok";
}

@Transactional
public void removePost() {
    postService.removeById(666L);
    int a = 1 / 0;
}

结果:

  • userService.removeById(666L) ✅ 成功
  • postService.removeById(666L) ❌ 失败
    原因:
  • 虽然 removePost()@Transactional,但直接调用导致注解失效
  • 代码在外层事务中执行
  • 异常被捕获,外层事务 不会回滚 (默认只对未捕获的 RuntimeException 回滚)

⚠️ 注意:这是最容易被误解的场景!

很多人以为"有 @Transactional 就会回滚",但直接调用时注解不生效。


示例8:REQUIRES_NEW + 直接调用(注解失效)

java 复制代码
@GetMapping("/test8")
@Transactional
public String test8() {
    userService.removeById(666L);
    try {
        removePostNew(); // 直接调用
    } catch (Exception e) {
        System.out.println("捕获异常");
    }
    return "ok";
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void removePostNew() {
    postService.removeById(666L);
    int a = 1 / 0;
}

结果:

  • userService.removeById(666L) ✅ 成功
  • postService.removeById(666L) ❌ 失败
    原因:
  • 直接调用 → @Transactional 失效
  • REQUIRES_NEW 不生效,代码仍在原事务中
  • 异常被捕获 → 外层事务不回滚

三、总结对比表

示例 调用方式 removePost有@Tx 传播行为 是否代理 事务生效 异常处理 user删除 post删除
1 直接 - - 未捕获 回滚 回滚
2 直接(private) - - 未捕获 回滚 回滚
3 直接 REQUIRED 未捕获 回滚 回滚
4 代理 REQUIRED 未捕获 回滚 回滚
5 代理 REQUIRES_NEW 捕获 成功 回滚
6 代理 NESTED 捕获 成功 回滚
7 直接 REQUIRED 捕获 成功 回滚
8 直接 REQUIRES_NEW 捕获 成功 回滚

四、核心结论

  1. 直接调用(this.method()

    • @Transactional 完全失效
    • 方法在调用者的事务中执行(如果有)
    • 异常是否回滚取决于外层事务配置
  2. 代理调用(proxy.method()

    • @Transactional 正常生效
    • 可按传播行为创建独立事务或嵌套事务
  3. 常见错误

    • 以为在同一个类中加了 @Transactional 就会自动开启新事务
    • 捕获异常后忘记重新抛出,导致事务不回滚
  4. 解决方案

    • 将需要独立事务的方法放到另一个 @Service 类中
    • 使用 AopContext.currentProxy() 获取代理对象
    • 使用 @Autowired 注入自身代理(需注意循环依赖)

五、最佳实践建议

  • 避免同一个类中的内部事务调用
  • ✅ 将事务方法拆分到不同的 Bean 中
  • ✅ 明确设置 @Transactional(rollbackFor = Exception.class)
  • ✅ 捕获异常后,如需回滚请调用 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
相关推荐
疯狂成瘾者2 小时前
什么是多 Agent,多Agent是如何协作的?
java
he___H2 小时前
Spring中的设计模式
java·spring·设计模式
liuyao_xianhui2 小时前
优选算法_最小基因变化_bfs_C++
java·开发语言·数据结构·c++·算法·哈希算法·宽度优先
做一个AK梦2 小时前
计算机系统概论知识点(软件设计师)
java·开发语言
東雪木2 小时前
Java学习——一访问修饰符(public/protected/default/private)的权限控制本质
java·开发语言·学习·java面试
两点王爷2 小时前
docker 创建和使用存储卷相关内容
java·docker·容器
boonya3 小时前
Embedding模型与向量维度动态切换完整方案
java·数据库·embedding·动态切换大模型
宁波阿成3 小时前
族谱管理系统架构分析与亮点总结
java·系统架构·vue·ruoyi-vue·族谱
姬成韶3 小时前
BUUCTF--[RoarCTF 2019]Easy Java
java·网络安全