1、分享背景
在项目开发中经常会用到具有事务的场景,而在我们的项目中主要是通过@Transaction 注解去实现事务,但是在使用的过程中可能会有一些不是特别合理的地方,不正当的使用事务可能会面临很多的问题,比如:事务失效,链接被耗光,甚至发生死锁的情况;
2、分享的目的
在以后的开发过程中能够更合理的使用事务,清晰的知道事务中每个参数的作用,以及整个事务的核心执行流程等;
3、在使用注解事务的过程中不甚合理的地方;
1、事务方法的粒度太大导致事务执行时间可能会很长;
2、事务中有跨服务调用的耗时行为;
3、事务中包含复杂的业务逻辑等;
4、事务常见的问题;
4.1、多个数据源怎么设置事务?
4.2、什么是只读事务,为什么需要只读事务?
4.3、怎么控制事务的传播行为?
4.4、怎么修改数据库的事务隔离级别?
4.5、事务方法中为什么不能有耗时业务?
5、事务的原理
原理比较简单,内部是通过spring aop的功能,通过拦截器拦截 @Transaction 方法的执行,在方法前后添加事务的功能。在Spring中TransactionInterceptor和PlatformTransactionManager这两个类是整个事务模块的核心,TransactionInterceptor负责拦截方法执行,进行判断是否需要提交或者回滚事务。PlatformTransactionManager是Spring 中的事务管理接口,真正定义了事务如何回滚和提交。
6、注解事务常用参数
返回文档
参数名称 | 功能描述 |
readOnly | 该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true) |
rollbackFor | 该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如: 指定单一异常类:@Transactional(rollbackFor=RuntimeException.class) 指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class}) |
rollbackForClassName | 该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如: 指定单一异常类名称:@Transactional(rollbackForClassName="RuntimeException") 指定多个异常类名称:@Transactional(rollbackForClassName={"RuntimeException","Exception"}) |
noRollbackFor | 该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如: 指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class) 指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class}) |
noRollbackForClassName | 该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如: 指定单一异常类名称:@Transactional(noRollbackForClassName="RuntimeException") 指定多个异常类名称: @Transactional(noRollbackForClassName={"RuntimeException","Exception"}) |
propagation | 该属性用于设置事务的传播行为,具体取值可参考表6-7。 例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) |
isolation | 该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置 |
timeout | 该属性用于设置事务的超时秒数,默认值为-1表示永不超时 |
7、事务使用过程中失效的场景或注意事项
1、只对public修饰方法才起作用
2、@Transaction默认检测异常为RuntimeException及其子类 如果有其他异常需要回滚事务的需要自己手动配置,例如:@Transactional(rollbackFor = Exception.class)
3、确保异常没有被try-catch{},catch以后也不会回滚
4、数据库层面,数据库使用的存储引擎是否支持事务?默认情况下MySQL数据库使用的是Innodb存储引擎(5.5版本之后),它是支持事务的,但是如果你的表特地修改了存储引擎,例如,你通过下面的语句修改了表使用的存储引擎为MyISAM,而MyISAM又是不支持事务的;
5、Springboot项目默认已经支持事务,不用配置;其他类型项目需要在xml中配置是否开启事务
SpringBoot框架默认开启了事务吗_springboot默认开启了事物吗_水晶果冻1125的博客-CSDN博客
6、如果在同一个类中,一个非@Transaction的方法调用有@Transaction的方法不会生效,因为代理问题
Ps: 一个非@Transaction的方法调用有@Transaction的方法不会生效。如果是在同一个类中的方法调用,则不会被方法拦截器拦截到,因此事务不会起作用,必须将方法放入另外一个类中,并且该类通过Spring注入。
Spring 采用动态代理(AOP)实现对Bean的管理和切片,它为我们的每个class生成一个代理对象,只有在代理对象之间进行调用时,可以触发切面逻辑。
而在同一个类中,方法B调用A,调用的事元对象的方法,而不是通过代理对象,所以spring无法切到这次调用,也就是无法通过注解保证事务性。
8、事务的传播机制
返回文档
事务传播行为类型 | 说明 |
PROPAGATION_REQUIRED | 业务方法需要在一个事务中运行,如果方法运行时,已处在一个事务中,那么就加入该事务,否则自己创建一个新的事务。这是spring默认的传播行为。 |
PROPAGATION_SUPPORTS | 该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被调用,该方法就在没有事务的环境下执行。 |
PROPAGATION_MANDATORY | 该方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果在没有事务的环境下被调用,容器抛出例外。 |
PROPAGATION_REQUIRES_NEW | 不管是否存在事务,该方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。 |
PROPAGATION_NOT_SUPPORTED
| 声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行。 |
PROPAGATION_NEVER | 该方法绝对不能在事务范围内执行。如果在就抛异常。只有该方法没有关联到任何事务,才正常执行。 |
PROPAGATION_NESTED
| 如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。 |
注意:这7中传播行为有个前提,他们的事务管理器是同一个的时候,才会有上面的表现行为;
9、源码解析
通过xml配置方式开启事务 <tx:annotation-driven /> xml
通过配置方式开启事务 @EnableTransactionManagement 代码
9.1、EnableTransactionManagement 源码解析
这个注解可以放在springboot的启动类上,也可以放在配置类上。他的作用是开启事务管理。他是spring-tx中的注解,不是springboot中的注解。如果你添加的是 spring-boot-starter-jdbc 依赖(mybatis框架也会依赖jdbc),框架会默认注入 DataSourceTransactionManager 实例。如果你添加的是 spring-boot-starter-data-jpa 依赖,框架会默认注入 JpaTransactionManager 实例。springboot的autoconfigure是默认已经有这个注解的,所以在springboot中不需要再次使用这个注解。
有了这个注解之后,spring启动过程中,会拦截所有的bean的创建过程,判断bean 是否需要让spring来管理事务,即判断bean中是否有@Transaction注解,判断规则如下
1、一直沿着当前bean的类向上找,先从当前类中,然后父类、父类的父类,当前类的接口、接口父接口,父接口的父接口,一直向上找,一下这些类型上面是否有 @Transaction注解
2、类的任意public方法上面是否有@Transaction注解
如果bean满足上面任意一个规则,就会被spring容器通过aop的方式创建代理,代理中会添加一个拦截器
org.springframework.transaction.interceptor.TransactionInterceptor
TransactionInterceptor 拦截器是关键,它会拦截@Transaction方法的执行,在方法执行前后添加事务的功能,这个拦截器中大部分都是编程式事务的代码.
@Import(TransactionManagementConfigurationSelector.class)
用到了@Import注解,这个注解的value是TransactionManagementConfigurationSelector,看一下这个类的源码,重点是他的selectImports方法,这个方法会返回一个类名数组,spring容器启动过程中会自动调用这个方法,将这个方法指定的类注册到spring容器中;方法的参数是AdviceMode,这个就是@EnableTransactionManagement注解中mode属性的值,默认是PROXY。
最终会在spirng容器中注册下面这2个bean
两个核心类:AutoProxyRegistrar ProxyTransactionManagementConfiguration
AutoProxyRegistrar (作用就是启用spring aop的功能,对符合条件的bean创建代理。)
这个类实现了ImportBeanDefinitionRegistrar接口,这个接口中有个方法registerBeanDefinitions,spring容器在启动过程中会调用这个方法,开发者可以在这个方法中做一些bean注册的事情,而AutoProxyRegistrar在这个方法中主要做的事情就是下面AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);的代码,这个代码的作用就是在容器中做了一个非常关键的bean:InfrastructureAdvisorAutoProxyCreator,这里是bean后置处理器,会拦截所有bean的创建,对符合条件的bean创建代理。
ProxyTransactionManagementConfiguration
if (this.txManager != null) { interceptor.setTransactionManager(this.txManager); } return interceptor; }}
是个配置类,代码比较简单,注册了3个bean,最重要的一点就是添加了事务事务拦截器:TransactionInterceptor。
AutoProxyRegistrar负责启用aop的功能,而ProxyTransactionManagementConfiguration负责在aop中添加事务拦截器,二者结合起来的效果就是:对@Transaction标注的bean创建代理对象,代理对象中通过TransactionInterceptor拦截器来实现事务管理的功能。
transactionAttributeSource() 方法注册了一个TransactionAttributeSource类型的bean
TransactionAttributeSource接口源码:
// 只有一个方法-返回给定方法的事务属性,如果该方法是非事务性的,则返回null。
getTransactionAttribute方法用来获取指定方法上的事务属性信息TransactionAttribute,大家对TransactionDefinition比较熟悉吧,用来配置事务属性信息的,而TransactionAttribute继承了TransactionDefinition接口,源码如下,而TransactionAttribute中新定义了2个方法,一个方法用来指定事务管理器bean名称的,一个用来判断给定的异常是否需要回滚事务
TransactionAttributeSource接口有个实现类AnnotationTransactionAttributeSource,负责将@Transaction解析为TransactionAttribute对象,大家可以去这个类中设置一下断点看一下@Transaction注解查找的顺序,这样可以深入理解@Transaction放在什么地方才会让事务起效。
AnnotationTransactionAttributeSource内部最会委托给SpringTransactionAnnotationParser#parseTransactionAnnotation方法来解析@Transaction注解,进而得到事务属性配置信息:RuleBasedTransactionAttribute,代码如下:
9.2、TransactionInterceptor
负责拦截@Transaction方法的执行,在方法执行之前开启spring事务,方法执行完毕之后提交或者回滚事务。
在讲这个类的源码之前,先提几个问题,
1、事务管理器是如何获取的?
2、什么情况下事务会提交?
3、什么异常会导致事务回滚?
1、invokeWithinTransaction方法
这个方法是事务拦截器的入口,需要spring管理事务的业务方法会被这个方法拦截
invokeWithinTransaction - 》》determineTransactionManager()--》》completeTransactionAfterThrowing(txInfo, ex);
determineTransactionManager
1、先看@Transactional中是否通过value或者transactionManager指定了事务管理器
2、TransactionInterceptor.transactionManagerBeanName是否有值,如果有,将通过这个值查找事务管理器
3、如果上面2种都没有,将从spring容器中查找TransactionManager类型的事务管理器
completeTransactionAfterThrowing()--》》rollbackOn--》》 rollback()
// 没有异常 - 直接提交
// 提交事务核心方法
到此,源码解析的差不多了,希望此次分享能给各位小伙伴带来一定的收获。