Transactional 原理
@Transactional 是 Spring 框架中用于实现事务管理的注解。事务是指一系列数据库操作,它们要么全部成功执行,要么全部失败回滚。使用 @Transactional 注解可以确保一组数据库操作要么全部成功提交,要么全部失败回滚,保持数据的一致性和完整性
事务Transaction
数据库事务是指一组数据库操作,这些操作被视为一个单独的工作单元,并且要么全部成功执行,要么全部失败回滚,以保持数据库的一致性和完整性
事务具有四个特性,通常称为 ACID 特性:
原子性(Atomicity):事务是一个不可分割的操作单元,要么全部执行成功,要么全部失败回滚。如果其中任何一个操作失败,整个事务将回滚到最初状态,不会留下部分完成的结果
一致性(Consistency):事务在执行前和执行后,数据库的状态必须保持一致。这意味着事务在执行后,数据库从一个一致的状态转换到另一个一致的状态
隔离性(Isolation):事务之间是相互隔离的,一个事务的执行不应该受到其他事务的干扰。即使多个事务同时执行,它们也不能相互干扰,每个事务都应该感觉自己在独立执行
持久性(Durability):一旦事务成功提交,其结果将被永久保存在数据库中,即使发生系统故障,数据也不会丢失
数据库事务的常用操作有:
开始事务(BEGIN 或 START TRANSACTION):开始一个新的事务,此时数据库会记录当前的状态
提交事务(COMMIT):如果事务中所有的操作都成功执行,那么就提交事务,将结果保存到数据库中
回滚事务(ROLLBACK):如果事务中的任何一个操作失败,回滚事务,将数据库状态恢复到事务开始之前的状态
事务的应用场景包括金融交易、订单处理、库存管理等需要确保数据完整性和一致性的场景。在数据库事务中,要注意设计良好的事务边界,避免事务持续时间过长,以免影响数据库的性能和并发处理能力
使用事务能够确保数据库操作的可靠性,是保障数据完整性和可靠性的重要手段。数据库管理系统(DBMS)负责实现和管理事务,并根据数据库的特性和事务的要求来确保 ACID 特性的实现
@Transactional
Spring中的 @Transactional 基于动态代理的机制,提供了一种透明的事务管理机制,方便快捷解决在开发中碰到的问题。在现实中,实际的问题往往比我们预期的要复杂很多,这就要求对 @Transactional 有深入的了解,以来应对复杂问题
在 Spring 中,@Transactional 注解可以用于类或方法级别。当应用在类级别上时,表示该类的所有公共方法都将受到事务管理。而当应用在方法级别上时,只有被注解的方法才会受到事务管理
要使用 @Transactional 注解,需要满足以下条件:
在 Spring 配置文件中启用事务管理器或者在启动类上添加
@EnableTransactionManagement在需要事务管理的类上或方法上添加
@Transactional注解
在Spring-boot里会自动配置事务,一般不需要手动开启事务管理
相关的配置在
org.boot.autoconfigure.transaction.TransactionAutoConfiguration
示例代码如下:
- 启用事务管理器:
1 | |
- 在类或方法上添加
@Transactional注解:
1 | |
在上面的示例中,doSomethingInTransaction 和 doSomethingWithRollback 方法都被 @Transactional 注解标记,这意味着它们会受到事务管理。如果在 doSomethingWithRollback 方法中发生了异常,事务将回滚,数据库操作将被撤销,保持数据的一致性
@EnableTransactionManagement
1 | |
与 @EnableAspectJAutoProxy 注解类似,都是向Spring导入了一个 TransactionManagementConfigurationSelector 组件,它是属于 ImportSelector 类型
1 | |
AutoProxyRegistrar

根据类的继承图可知,它属于 ImportBeanDefinitionRegistrar 类型,Spring最终会调用内部的registerBeanDefinitions 方法,下述是源码中比较关键的代码
1 | |
在使用 @EnableTransactionManagement 注解时,并没有特意去修改内部方法的返回值。因此,这里的mode就是AdviceMode.PROXY,proxyTargetClass为false。由源码逻辑可知:Spring最终会执行这段代码:AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry) 这段代码的主要功能就是向Spring容器中添加这个bean:InfrastructureAdvisorAutoProxyCreator.class

与之前 AOP 中的 @EnableAspectJAutoProxy 内部导入的 AnnotationAwareAspectJAutoProxyCreator bean的类继承图十分相似,都继承了 AbstractAdvisorAutoProxyCreator
整个Spring AOP寻找切面、切面、通知的过程就是此方法 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 的功劳,而生成AOP代理对象就是 BeanPostProcessor#postProcessAfterInitialization 的功劳。而这些寻找切面、生成代理对象的功能其实是抽象父类 AbstractAutoProxyCreator 的功能。因此,InfrastructureAdvisorAutoProxyCreatorv也具备了寻找切面、切面、通知以及生成代理对象的功能
ProxyTransactionManagementConfiguration

1 | |
这个类主要功能:
- 构建事务拦截器
transactionInterceptor - 构建事务属性数据源
transactionAttributeSource - 构建 Advisor 类型的 bean
transactionAdvisor
事务实现
编程式事务
所谓编程式事务就是手动在代码中完成事务的提交,发生异常时的回滚。
在实现类中注入PlatformTransactionManager
1 | |
声明式事务
所谓声明式事务,就是使用@Transactional注解开启事务,该注解可以放在类上和方法上,放在类上时,该类所有的public方法都会开启事务;放在方法上时,表示当前方法支持事务
1 | |
@Transactional 底层执行原理
ProxyTransactionManagementConfiguration 在注入BeanFactoryTransactionAttributeSourceAdvisor 的同时,还为其设置了事务增强器:advisor.setAdvice(transactionInterceptor;
1 | |
TransactionInterceptor

TransactionInterceptor 继承自 TransactionAspectSupport,并实现了 MethodInterceptor、Serializable;说明它是一个方法拦截器,在声明式事务中,其实就是在IoC容器中注册一个代理对象,当代理对象要执行目标方法时,方法拦截器会工作:
org.springframework.transaction.interceptor.TransactionInterceptor#invoke
1 | |
关于事务开启、回滚、提交的逻辑就是在 invokeWithinTransaction() 方法中实现
1 | |
completeTransactionAfterThrowing() 执行过程中发生异常,执行回滚方法
org.springframework.transaction.interceptor.TransactionAspectSupport#completeTransactionAfterThrowing
1 | |
commitTransactionAfterReturning() 正常执行,提交事务方法
org.springframework.transaction.interceptor.TransactionAspectSupport#commitTransactionAfterReturning
1 | |
Transactional 常用属性配置
propagation
@Transactional(propagation = Propagation.REQUIRED)
配置事务的传播特性,默认为:required
| 传播性 | 描述 |
|---|---|
| Propagation**.REQUIRED** | (默认值)如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置 |
| Propagation**.REQUIRES_NEW** | 创建新事务,无论当前存不存在事务,都创建新事务,如果当前有事务就将当前事务挂起 |
| Propagation**.SUPPORTS** | 如果有事务就在当前事务下运行,没有事务就在无事务状态下运行 |
| Propagation**.NOT_SUPPORTED** | 在无事务状态下运行,如果有事务,将当前事务挂起 |
| Propagation**.MANDATORY** | 必须存在事务,若无事务,抛出异常IllegalTransactionStateException |
| Propagation**.NEVER** | 不支持事务,有事务就抛出异常 |
| Propagation**.NESTED** | 当前有事务就在当事务里面再起一个事务 |
- 支持当前事务
int PROPAGATION_REQUIRED = 0; 如果当前存在事务,则加入当前事物;如果当前事务不存在,则新建一个事务;
int PROPAGATION_SUPPORTS = 1; 如果当前存在事务,则加入当前事务;如果当前事务不存在,则以非事务方式运行;
int PROPAGATION_MANDATORY = 2; 如果当前存在事务,则加入当前事务;如果当前事务不存在,则抛出异常;
- 不支持当前事务
int PROPAGATION_REQUIRES_NEW = 3; 创建一个新事物,如果当前存在新事物,则把当前事务挂起;
int PROPAGATION_NOT_SUPPORTED = 4; 以非事务方式运行,如果当前存在事务,则把当前事务挂起;
int PROPAGATION_NEVER = 5; 以非事务方式运行,如果当前存在事务,则抛出异常;
int PROPAGATION_NESTED = 6; 如果当前存在事务,则创建一个事务作为嵌套事务运行,如果当前不存在事务,则参照PROPAGATION_REQUIRED
对于NESTED事务,如果外部事务异常,则内部事务也必须回滚
isolation
配置事务个隔离级别,默认为当前数据库的默认隔离级别(MySQL为REPEATABLE-READ)
@Transactional(isolation = Isolation.READ_COMMITTED )
查看数据的隔离级别

| 隔离性 | 描述 |
|---|---|
| READ UNCOMMITTED(未提交度) | 读取未提交内容,所有事务可看到其他未提交事务的结果,很少实际使用(会出现脏读) |
| READ COMMITTED(提交读) | 一个事务只能读取到另一个事务已提交事务的修改过的数据,并且其他事务每次对数据进行修改并提交后,该事务都能查询到最新值 |
| REPEATABLE READ(可重复读) | 一个是事务读取其实事务已经提交的修改数据,第一次读取某条记录时,即时其他事务修改了该记录并提交时,之后再读取这条记录时,仍然是第一次读取的值,而不是每次读取不同的数据 |
| SERIALIZABLE(串行化) | 事务串行化执行,不会出现踩踏,避免了脏读、幻读和不可重复度,但效率低下 |
timeout
配置事务超时时间,默认为:-1
@Transactional(timeout = 5)
指定强制回滚之前事务可以占用的时间。单位:秒。如果执行时间草果这个时间就强制回滚
readOnly
@Transactional(readOnly = false)
默认为 :false
指定事务是否为只读,表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务。如果只有查询数据操作, 应设置 readOnly = true
rollbackFor
配置在那些异常情况下需要回滚数据,默认情况只在发生不受控异常下回滚(RuntimeException和Error),开发中最好配置为Exception
受控异常(checked exceptions):就是非运行时异常,即Exception中除了RuntimeException及其子类以外的
不受控异常(unchecked exceptions):RuntimeException和Error
rollbackFor 属性在这里就可以发挥它的作用了
@Transactional(rollbackFor = Exception.class)
这里你可以使用 java 已声明的异常;也可以使用自定义异常;也可同时设置多个异常类,中间用逗号间隔
@Transactional(rollbackFor = {SQLException.class,UserAccountException.class})
noRollbackFor
默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚。可以通过noRollbackFor属性进行设置例外异常
@Transactional(noRollbackFor = {UserAccountException.class})
上面设置了遇到UserAccountException异常不回滚。一般不建议设置这个属性,通常情况下默认即可
注意的几点
@Transactional只能被应用到public方法上,对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能- 用 spring 事务管理器,由spring来负责数据库的打开、提交、回滚。默认遇到运行期例外(
throw new RuntimeException("注释");)会回滚,即遇到不受检查(unchecked)的例外时回滚;而遇到需要捕获的例外(throw new Exception("注释");)不会回滚,即遇到受检查的例外时,需我们指定方式来让事务回滚要想所有异常都回滚,要加上@Transactional( rollbackFor={Exception.class,其它异常})。如果让unchecked例外不回滚:@Transactional(notRollbackFor=RunTimeException.class) @Transactional注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。然而,请注意仅仅@Transactional注解的出现不足于开启事务行为,它仅仅是一种元数据,能够被可以识别@Transactional注解和上述的配置适当的具有事务行为的 beans 所使用- Spring 团队的建议是你在具体的类(或类的方法)上使用
@Transactional注解,而不要使用在类所要实现的任何接口上。你当然可以在接口上使用@Transactional注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因此,请接受Spring团队的建议并且在具体的类上使用@Transactional注解
事务失效
**
propagation**设置错误,包括三种:Propagation.SUPPORTS,Propagation.NOT_SUPPORTED,Propagation.NEVERA,B两个方法,B方法添加了
@Transactional,A没有,A中调用了B,如果在调用A方法的过程中,无论是A出问题还是B出问题,抛出异常,都不会回滚,是因为SpringAOP,只有当前事务方法被当前类以外的代码调用时,才会生产代理对象加了
try-catch,没有抛出异常两个Service,ServiceA和ServiceB。两个都有
@Transactional,A中调用了B,B抛出异常,这时B标识为需要回滚,但是A方法try-catch这个异常并进行了处理,A标识为可以commit,就会报错UnexpectedRollbackException数据库引擎不支持事务
rollbackFor设置错误抛出检查异常导致事务不能正确回滚(没写
rollbackFor,或者try-catch掉了没到达外层环绕通知,必须抛出去或者transactionStatus.setRollbackOnly())自定义切面捕获了内层异常(因为优先级问题 事务切面在最外层,可以更改自定义切面order 「不推荐」)
@Transactional需要加在公共方法上,spring为方法创建代理,添加事务通知前提都是public(解决办法,**public** 或者annotationTranscationAttributeSource(publicMethodsOnly:true))父子容器事务失效,子容器扫描范围过大,将未配置事务控制的service扫描进来(springboot一般不会出现,因为只有一个容器)
调用本类方法导致传播行为失效,因为调用本类方法不经过代理,因此无法增强(依赖注入自己(代理)调用;通过
aopcontext拿到代理对象来调用;通过CTW,LTW实现功能增强@Transactional没有保证原子行为,**select**方法并不阻塞,事务的原子性仅涵盖insert update delete select... for update@Transactional方法导致synchronized失效,**synchronized** 仅仅保证目标方法的原子性,环绕目标方法的还有commit等操作,他们并未处于sync块内(扩大synchronized范围至代理方法调用;使用select...for update替换select