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.NEVER
A,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