聊聊@Transactional

一、@Transactional 是什么?

@Transactional 是 Spring 框架提供的声明式事务管理注解,它能将多个数据库操作包装成一个原子性的工作单元------要么全部成功提交,要么全部失败回滚。

以用户注册为例:我们需要向用户表插入记录、初始化用户积分、发送欢迎消息。使用 @Transactional 后,如果积分初始化失败,用户记录会自动回滚,避免产生"僵尸用户"。

事务注解标注在方法或类上,Spring 容器会在运行时自动创建代理对象,在目标方法执行前后加入事务管理逻辑。

二、@Transactional 不是数据库连接管理

很多人误以为 @Transactional 是"数据库连接池"或"SQL 执行器",其实完全不同:

  • 数据库连接:负责与数据库建立通信,执行 SQL 语句
  • 事务管理:保证一组 SQL 操作的原子性、一致性、隔离性和持久性(ACID)

Spring 事务背后依赖两大核心技术:

  • AOP(面向切面编程) :通过动态代理在方法执行前后插入事务逻辑
  • 事务同步管理器 :通过 ThreadLocal 将事务资源绑定到当前线程

这使得多个数据库操作可以在同一个事务上下文中协同工作。

三、事务传播机制:灵活的事务边界控制

Spring 提供了 7 种事务传播行为,例如:

java 复制代码
@Transactional(propagation = Propagation.REQUIRED)      // 默认:存在则加入,不存在则新建
@Transactional(propagation = Propagation.REQUIRES_NEW)  // 总是新建独立事务
@Transactional(propagation = Propagation.NESTED)        // 嵌套事务,支持部分回滚

每个传播级别对应不同的业务场景:

  • REQUIRED:大多数业务方法,需要事务保障
  • REQUIRES_NEW:日志记录、消息发送等,不受主事务影响
  • NESTED:复杂业务流程,允许子流程独立回滚

四、如何使用 @Transactional?

1. 单方法事务控制

java 复制代码
@Service
public class UserService {
    
    @Transactional
    public void registerUser(User user) {
        userDao.insert(user);          // 插入用户
       积分Service.initPoints(user.getId()); // 初始化积分
        messageService.sendWelcome(user.getEmail()); // 发送邮件
    }
}

如果需要自定义事务行为(如超时时间、隔离级别),通过注解参数配置:

  • timeout:事务超时时间(秒)
  • isolation:事务隔离级别
  • rollbackFor:指定回滚的异常类型

2. 多方法事务协调

复杂业务通常涉及多个服务协作。Spring 通过事务传播机制自动管理:

java 复制代码
@Service
@Transactional
public class OrderService {
    
    @Autowired
    private InventoryService inventoryService;
    
    public void createOrder(Order order) {
        orderDao.save(order);                    // 主事务中执行
        inventoryService.deductStock(order);     // 根据传播设置决定事务行为
        paymentService.processPayment(order);    // 可能在新事务中执行
    }
}

服务间的事务边界由传播级别决定,Spring 自动完成事务的挂起、恢复和资源同步。

为什么同类方法调用导致 @Transactional 失效?

在 Spring 框架中,@Transactional 注解用于声明式事务管理,非常方便。但有一个常见的"陷阱":

当一个类中的方法 A 调用同一个类中的另一个带有 @Transactional 注解的方法 B 时,B 上的事务注解不会生效。

无论 B 的事务传播行为是 REQUIREDREQUIRES_NEW 还是 NESTED,只要它是被同一个类的内部方法直接调用(即"自调用") ,Spring 的事务机制就无法拦截到这个调用,因此事务不会按预期工作。


根本原因:Spring 事务基于代理(Proxy)

Spring 的 @Transactional 是通过 AOP(面向切面编程) 实现的,默认使用 JDK 动态代理CGLIB 代理

  • 当你从 Spring 容器中获取一个 Bean(比如 MyService),你拿到的其实是一个 代理对象
  • 当你调用 myService.methodA() 时,代理对象会先检查该方法是否有 @Transactional,如果有,就开启事务,再调用目标方法。
  • 但是 :如果 methodA() 内部直接调用 this.methodB(),这个调用是绕过代理对象 的,直接在原始对象(target)上执行,AOP 拦截器根本不会介入

因此:

  • methodB() 上的 @Transactional 不会被 Spring 感知到
  • 事务行为完全由外层方法(如 methodA())决定(如果它有事务的话)。

@Transactionalprivatefinalstatic 方法无效(因为代理无法覆盖)。

默认只对 unchecked exception(RuntimeException 及 Error) 回滚,checked exception 不回滚(除非配置 rollbackFor)。

使用 REQUIRES_NEWNESTED 时,务必确保调用路径经过代理,否则行为不符合预期。

相关推荐
颜酱7 分钟前
# 02 | 搭骨架:用 LangGraph 编排 12 步工作流(思路)
前端·人工智能·后端
颜酱8 分钟前
02 | 搭骨架:用 LangGraph 编排 12 步工作流
前端·人工智能·后端
嘉伟桑1 小时前
Python调用电价API返回JSON示例:分时电价、现货电价和字段解析
后端
武子康1 小时前
Shippy:确定性工具、会话级 Sandbox 与 Live-Data Eval(4 类收敛 + 7 步实现方案 + 6 类评测指标)
前端·人工智能·后端
我叫黑大帅2 小时前
我为什么单一消费者的场景下,要用 Redis List 当消息队列?
redis·后端·面试
AskHarries2 小时前
文件上传系统
后端
止语Lab3 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活3 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困3 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn3 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端