聊聊@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 时,务必确保调用路径经过代理,否则行为不符合预期。

相关推荐
Victor35610 小时前
https://editor.csdn.net/md/?articleId=139321571&spm=1011.2415.3001.9698
后端
Victor35610 小时前
Hibernate(89)如何在压力测试中使用Hibernate?
后端
灰子学技术11 小时前
go response.Body.close()导致连接异常处理
开发语言·后端·golang
Gogo81612 小时前
BigInt 与 Number 的爱恨情仇,为何大佬都劝你“能用 Number 就别用 BigInt”?
后端
fuquxiaoguang12 小时前
深入浅出:使用MDC构建SpringBoot全链路请求追踪系统
java·spring boot·后端·调用链分析
毕设源码_廖学姐13 小时前
计算机毕业设计springboot招聘系统网站 基于SpringBoot的在线人才对接平台 SpringBoot驱动的智能求职与招聘服务网
spring boot·后端·课程设计
野犬寒鸦14 小时前
从零起步学习并发编程 || 第六章:ReentrantLock与synchronized 的辨析及运用
java·服务器·数据库·后端·学习·算法
逍遥德15 小时前
如何学编程之01.理论篇.如何通过阅读代码来提高自己的编程能力?
前端·后端·程序人生·重构·软件构建·代码规范
MX_935916 小时前
Spring的bean工厂后处理器和Bean后处理器
java·后端·spring
程序员泠零澪回家种桔子17 小时前
Spring AI框架全方位详解
java·人工智能·后端·spring·ai·架构