@Transactional 事务注解
@Transactional 事务注解
- 1. @Transactional 是什么?
- 2. @Transactional什么时候使用?
- 3. @Transactional 的核心原理
- 4. 常见属性
- 4.1 propagation (事务传播行为)
- 4.2 isolation (隔离级别)
- 4.3 timeout
- 4.4 readOnly
- 4.5 rollbackFor / rollbackForClassName
- 4.6 noRollbackFor / noRollbackForClassName
- 5. 默认回滚机制
- 6. 示例代码
- 7. 常见坑点
- 7.1 事务方法调用自身不会生效
- 8. 最佳实践
- 9. 举例事务不生效
- 附录
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. 示例代码
@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 事务方法调用自身不会生效
@Service
public class UserService {@Transactionalpublic void addUser() {// 插入用户saveUser();// 调用另一个事务方法updateUser();}@Transactionalpublic void updateUser() {// 更新用户}
}
问题:内部调用不会走代理
在 addUser()
里调用 updateUser()
时,调用方式是:
this.updateUser();
- 这里的 this 是当前对象(原始对象),不是代理对象。
- 因此,Spring 的 AOP 拦截器(TransactionInterceptor)不会介入。
- 结果:updateUser() 上的 @Transactional 不会生效,只是普通方法调用。
- 如果 addUser() 没有 @Transactional,但 updateUser() 有:
- 从外部调用 addUser() 时,updateUser() 的事务不会生效。
- 如果 addUser() 有事务,updateUser() 也有事务:
- 实际上,整个调用都在 addUser() 的事务中,updateUser() 的传播行为被忽略。
解决办法
@Service
public class UserService {@Autowiredprivate UserHelperService helper;@Transactionalpublic void addUser() {saveUser();// 可以解决自身调用事务不生效的问题,本质是通过 代理对象 调用SpringUtils.getBean(UserService.class).updateUser()}
}@Service
public class UserHelperService {@Transactionalpublic void updateUser() {// 更新逻辑}
}
8. 最佳实践
- 事务边界尽量放在 Service 层(而不是 Controller/DAO)。
- 保持事务尽量短小,避免长时间占用锁。
- 明确指定 rollbackFor,避免因受检异常导致事务未回滚。
- 避免在事务中调用远程服务(如 HTTP、RPC),防止长事务。
9. 举例事务不生效
解决问题方法
附录
1.Spring @Transactional 详解:何时使用、为什么使用、如何使用 https://mp.weixin.qq.com/s/pbJllQXG9yN5liiJ6Ywbsw
2.工作 6 年,@Transactional 注解用的一塌糊涂! https://mp.weixin.qq.com/s/Tv0juKbCFqSyXedM2tO3hA