@Transactional 事务注解

@Transactional 事务注解

  • [1. @Transactional 是什么?](#1. @Transactional 是什么?)
  • [2. @Transactional什么时候使用?](#2. @Transactional什么时候使用?)
  • [3. @Transactional 的核心原理](#3. @Transactional 的核心原理)
  • [4. 常见属性](#4. 常见属性)
    • [4.1 propagation (事务传播行为)](#4.1 propagation (事务传播行为))
    • [4.2 isolation (隔离级别)](#4.2 isolation (隔离级别))
    • [4.3 timeout](#4.3 timeout)
    • [4.4 readOnly](#4.4 readOnly)
    • [4.5 rollbackFor / rollbackForClassName](#4.5 rollbackFor / rollbackForClassName)
    • [4.6 noRollbackFor / noRollbackForClassName](#4.6 noRollbackFor / noRollbackForClassName)
  • [5. 默认回滚机制](#5. 默认回滚机制)
  • [6. 示例代码](#6. 示例代码)
  • [7. 常见坑点](#7. 常见坑点)
    • [7.1 事务方法调用自身不会生效](#7.1 事务方法调用自身不会生效)
  • [8. 最佳实践](#8. 最佳实践)
  • [9. 举例事务不生效](#9. 举例事务不生效)
  • 10. @Transactional失效场景,对#7的补充
  • [11. 不要在@Transactional做外部调用](#11. 不要在@Transactional做外部调用)
  • 附录

1. @Transactional 是什么?

  • @TransactionalSpring 提供的事务注解,用于声明式事务管理。
  • 它可以用在 方法(public) 上,用来声明该方法/类中的数据库操作需要在事务中执行。
  • 当方法执行过程中出现异常时,可以根据配置进行 事务回滚,保证数据一致性。

2. @Transactional什么时候使用?

以下场景建议使用 @Transactional:

当你需要执行多个数据库操作,且这些操作必须具备原子性(要么全部成功,要么全部失败)。

  • 需要在出现错误或异常时自动回滚操作。
  • 希望避免手动管理事务(比如直接使用 JDBC 或 EntityManager 的事务 API)。

3. @Transactional 的核心原理

Spring 的事务管理主要基于 AOP(面向切面编程) + 数据库的事务支持。

  1. 代理机制
  • Spring 为标注了 @Transactional 的方法生成一个代理类。
  • 在方法执行前,代理类会开启事务;执行后,提交或回滚事务。
  1. 事务管理器
  • Spring 使用 PlatformTransactionManager 来统一管理事务(如 DataSourceTransactionManager 对 JDBC,JpaTransactionManager 对 JPA)。
  1. 数据库事务
  • 最终还是依赖数据库本身的事务机制(ACID)。

4. 常见属性

4.1 propagation (事务传播行为)

定义方法在事务环境中的运行方式。常见值:

传播行为 说明
REQUIRED(默认) 如果存在事务,加入当前事务;没有事务则新建一个。
REQUIRES_NEW 不管是否有事务,都会新建一个事务;原事务会挂起。
SUPPORTS 有事务就加入,没有就以非事务方式运行。
NOT_SUPPORTED 总是非事务运行,如果有事务则挂起。
MANDATORY 必须在事务中运行,否则抛异常。
NEVER 必须在非事务中运行,否则抛异常。
NESTED 嵌套事务(依赖于 JDBC 的 savepoint 实现),内层回滚不影响外层。

4.2 isolation (隔离级别)

定义事务之间数据访问的隔离性。对应数据库隔离级别:

隔离级别 说明
DEFAULT(默认) 使用数据库默认隔离级别。
READ_UNCOMMITTED 允许读取未提交数据(脏读)。
READ_COMMITTED 只能读取已提交数据(避免脏读)。
REPEATABLE_READ 多次读取结果一致(避免脏读、不可重复读)。
SERIALIZABLE 串行执行事务,避免所有并发问题,但效率最低。

4.3 timeout

  • 事务超时时间(秒),默认使用数据库默认超时。
  • 超时后事务会回滚。

4.4 readOnly

  • true:只读事务,通常用于查询。
  • 提示数据库优化器可优化,不会修改数据。

4.5 rollbackFor / rollbackForClassName

  • 指定遇到哪些 异常类 时回滚事务(默认只对运行时异常 RuntimeException 回滚)。

4.6 noRollbackFor / noRollbackForClassName

  • 指定遇到哪些异常时 不回滚。

5. 默认回滚机制

  • 默认情况下:

    • 运行时异常(RuntimeException)和 Error → 会回滚。
    • 受检异常(CheckedException) → 不会回滚。
  • 可以通过 rollbackFor 来修改默认规则。

6. 示例代码

java 复制代码
@Service
public class UserService {

    @Transactional(
        propagation = Propagation.REQUIRED,
        isolation = Isolation.READ_COMMITTED,
        rollbackFor = Exception.class,
        timeout = 5
    )
    public void registerUser(User user) {
        userMapper.insert(user);
        // 可能抛出异常
        if (user.getName() == null) {
            throw new IllegalArgumentException("用户名不能为空");
        }
        accountMapper.createAccount(user.getId());
    }
}

7. 常见坑点

  1. 事务方法调用自身不会生效

    • 因为事务依赖代理,内部调用不会经过代理,所以事务不起作用。
    • 解决办法:将方法拆到不同的 @Service 类,或者通过 AopContext.currentProxy() 调用。
  2. private 方法不生效

    • Spring AOP 只能代理 public 方法,private/protected 方法不会被增强。
  3. 异步方法事务不生效

    • 异步调用(@Async)会新建线程,不在同一个事务上下文。
  4. 只读事务不是强制的

    • readOnly = true 并不会阻止写操作,只是数据库可能做优化。
  5. 被用 final 、static 修饰的方法上加 @Transactional 也不会生效。

7.1 事务方法调用自身不会生效

java 复制代码
@Service
public class UserService {

    @Transactional
    public void addUser() {
        // 插入用户
        saveUser();

        // 调用另一个事务方法
        updateUser();
    }

    @Transactional
    public void updateUser() {
        // 更新用户
    }
}

问题:内部调用不会走代理

addUser() 里调用 updateUser() 时,调用方式是:

java 复制代码
this.updateUser();
  • 这里的 this 是当前对象(原始对象),不是代理对象。
  • 因此,Spring 的 AOP 拦截器(TransactionInterceptor)不会介入。
  • 结果:updateUser() 上的 @Transactional 不会生效,只是普通方法调用。
  1. 如果 addUser() 没有 @Transactional,但 updateUser() 有:
    • 从外部调用 addUser() 时,updateUser() 的事务不会生效。
  2. 如果 addUser() 有事务,updateUser() 也有事务:
    • 实际上,整个调用都在 addUser() 的事务中,updateUser() 的传播行为被忽略。

解决办法

java 复制代码
@Service
public class UserService {

    @Autowired
    private UserHelperService helper;

    @Transactional
    public void addUser() {
        saveUser();
        // 可以解决自身调用事务不生效的问题,本质是通过 代理对象 调用
        SpringUtils.getBean(UserService.class).updateUser()
    }
}

@Service
public class UserHelperService {
    @Transactional
    public void updateUser() {
        // 更新逻辑
    }
}

8. 最佳实践

  • 事务边界尽量放在 Service 层(而不是 Controller/DAO)。
  • 保持事务尽量短小,避免长时间占用锁。
  • 明确指定 rollbackFor,避免因受检异常导致事务未回滚。
  • 避免在事务中调用远程服务(如 HTTP、RPC),防止长事务。

9. 举例事务不生效


解决问题方法

10. @Transactional失效场景,对#7的补充

  1. rollbackFor 默认只回滚RunTimeException与Error,抛个IOException都会回滚失败。
  2. 异常如果仅仅只catch而没有抛出,回滚也会失败。
  3. 同类方法互相调用,没有走代理,事务根本没有生效。如#7.1
  4. 在非public方法上会失效。因为@Transactional事务是基于Spring AOP实现的而Spring AOP默认只拦截public方法的调用。
  5. 在final or static修饰的方法上也会失效。cglib代理无法对final方法子类化。static是静态方法它属于类不属于实例对象,无法被代理。
  6. 事务传播属性配置错误。如下,不管是否有事务,都会新建一个事务;原事务会挂起。,两个方法都不在一个事务上,当然无法保证数据的一致性。
  1. 多线程@Transactional是基于ThreadLocal存储上下问的,如果线程一变,事务就断了。也就失效了。

  2. 如果数据库使用MySQL的MyISAM引擎,那么根本就不支持事务。

11. 不要在@Transactional做外部调用

很多人为了方便会在@Transactional中去查询或者更新缓存,或者调用外部的RPC接口,再或者是发一个MQ消息,竟然还有人说,如果我这个地方调用外部服务失败了,我本地事务就回滚,也能报纸一致性,如果你是这么想的就打错特错了,如果你在事务中做远程调用,会因为网络交互,当中存在的网络延迟以及下游服务的响应慢等,而导致你的事务变成长事务,会占用数据库的连接池。还有外部调用有可能超时,带来事务的回滚,但是超时的外部调用可能后面自己慢慢处理成功了。这就导致了事务的不一致。

我们可以先去调用外部服务,通了之后再去做开启事务本地操作。如果调用失败了或者本地事务操作失败了,再进行事务的补偿,调用外部的接口再进行回滚。

附录

1.Spring @Transactional 详解:何时使用、为什么使用、如何使用 https://mp.weixin.qq.com/s/pbJllQXG9yN5liiJ6Ywbsw

2.工作 6 年,@Transactional 注解用的一塌糊涂! https://mp.weixin.qq.com/s/Tv0juKbCFqSyXedM2tO3hA

相关推荐
TDengine (老段)3 小时前
TDengine 数学函数 DEGRESS 用户手册
大数据·数据库·sql·物联网·时序数据库·iot·tdengine
TDengine (老段)3 小时前
TDengine 数学函数 GREATEST 用户手册
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据
安当加密4 小时前
云原生时代的数据库字段加密:在微服务与 Kubernetes 中实现合规与敏捷的统一
数据库·微服务·云原生
爱喝白开水a4 小时前
LangChain 基础系列之 Prompt 工程详解:从设计原理到实战模板_langchain prompt
开发语言·数据库·人工智能·python·langchain·prompt·知识图谱
想ai抽4 小时前
深入starrocks-多列联合统计一致性探查与策略(YY一下)
java·数据库·数据仓库
武子康4 小时前
Java-152 深入浅出 MongoDB 索引详解 从 MongoDB B-树 到 MySQL B+树 索引机制、数据结构与应用场景的全面对比分析
java·开发语言·数据库·sql·mongodb·性能优化·nosql
longgyy4 小时前
5 分钟用火山引擎 DeepSeek 调用大模型生成小红书文案
java·数据库·火山引擎
ytttr8735 小时前
C# 仿QQ聊天功能实现 (SQL Server数据库)
数据库·oracle·c#
盒马coding6 小时前
第18节-索引-Partial-Indexes
数据库·postgresql
dingdingfish6 小时前
关于Oracle RAC和ADG的学习资料
oracle·database·adg·rac·ha·dr·maa