一起聊聊@Transaction 注解的那些坑

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()

 

 

// 没有异常 - 直接提交

 // 提交事务核心方法

到此,源码解析的差不多了,希望此次分享能给各位小伙伴带来一定的收获。

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值