@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 是什么?
@Transactional
是 Spring 提供的事务注解,用于声明式事务管理。- 它可以用在 类 或 方法(public) 上,用来声明该方法/类中的数据库操作需要在事务中执行。
- 当方法执行过程中出现异常时,可以根据配置进行 事务回滚,保证数据一致性。
2. @Transactional什么时候使用?
以下场景建议使用 @Transactional:
当你需要执行多个数据库操作,且这些操作必须具备原子性(要么全部成功,要么全部失败)。
- 需要在出现错误或异常时自动回滚操作。
- 希望避免手动管理事务(比如直接使用 JDBC 或 EntityManager 的事务 API)。
3. @Transactional 的核心原理
Spring 的事务管理主要基于 AOP(面向切面编程) + 数据库的事务支持。
- 代理机制
- Spring 为标注了 @Transactional 的方法生成一个代理类。
- 在方法执行前,代理类会开启事务;执行后,提交或回滚事务。
- 事务管理器
- Spring 使用 PlatformTransactionManager 来统一管理事务(如 DataSourceTransactionManager 对 JDBC,JpaTransactionManager 对 JPA)。
- 数据库事务
- 最终还是依赖数据库本身的事务机制(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. 常见坑点
-
事务方法调用自身不会生效
- 因为事务依赖代理,内部调用不会经过代理,所以事务不起作用。
- 解决办法:将方法拆到不同的 @Service 类,或者通过 AopContext.currentProxy() 调用。
-
private 方法不生效
- Spring AOP 只能代理 public 方法,private/protected 方法不会被增强。
-
异步方法事务不生效
- 异步调用(@Async)会新建线程,不在同一个事务上下文。
-
只读事务不是强制的
readOnly = true
并不会阻止写操作,只是数据库可能做优化。
-
被用 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 不会生效,只是普通方法调用。
- 如果 addUser() 没有 @Transactional,但 updateUser() 有:
- 从外部调用 addUser() 时,updateUser() 的事务不会生效。
- 如果 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的补充
- rollbackFor 默认只回滚RunTimeException与Error,抛个IOException都会回滚失败。
- 异常如果仅仅只catch而没有抛出,回滚也会失败。
- 同类方法互相调用,没有走代理,事务根本没有生效。如
#7.1
- 在非public方法上会失效。因为
@Transactional
事务是基于Spring AOP
实现的而Spring AOP默认只拦截public方法的调用。 - 在final or static修饰的方法上也会失效。
cglib
代理无法对final方法子类化。static是静态方法它属于类不属于实例对象,无法被代理。 - 事务传播属性配置错误。如下,不管是否有事务,都会新建一个事务;原事务会挂起。,两个方法都不在一个事务上,当然无法保证数据的一致性。

-
多线程
@Transactional
是基于ThreadLocal存储上下问的,如果线程一变,事务就断了。也就失效了。
-
如果数据库使用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