一文搞懂spring事务
1. 事务运行的基本原理
1.1 从注解@EnableTransactionManagement开始
-
springboot开启事务的入口EnableTransactionManagement
如果开发者要使用spring事务,需要通过这个注解开启。这个注解也成为我们了解Spring事务的第一步。那么这个引用注解到底为我们做了什么事情呢 -
EnableTransactionManagement源码解析
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(TransactionManagementConfigurationSelector.class) public @interface EnableTransactionManagement { boolean proxyTargetClass() default false; AdviceMode mode() default AdviceMode.PROXY; int order() default Ordered.LOWEST_PRECEDENCE; }
这里注解上有一个 @Import(TransactionManagementConfigurationSelector.class)
spring容器启动时会检查@Import注解,如果有这个注解的,会扫描对应的类,我们来看看这个类做了什么public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> { @Override protected String[] selectImports(AdviceMode adviceMode) { switch (adviceMode) { case PROXY: // 默认是PROXY // 将下面的两个bean注册到spring容器中 return new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()}; case ASPECTJ: // 表示不用动态代理技术,用ASPECTJ技术 return new String[] {determineTransactionAspectClass()}; default: return null; } } private String determineTransactionAspectClass() { return (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader()) ? TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME : TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME); } }
这里 TransactionManagementConfigurationSelector 继承了AdviceModeImportSelector,AdviceModeImportSelector实现了ImportSelector接口,其继承关系如下:
spring扫描import的类时,如果该类实现了ImportSelector接口,会调用selectImports()方法,将方法返回的类名对应的类注册到容器中。TransactionManagementConfigurationSelector 重写了selectImports(),也就是注册了两个重要的Bean: AutoProxyRegistrar和ProxyTransactionManagementConfiguration
-
AutoProxyRegistrar
@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // ...省略非关键代码 if (mode == AdviceMode.PROXY) { // 这个方法再往下层追,注册了一个beanInfrastructureAdvisorAutoProxyCreator // registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source) // 注册了这个bean,aop才会代理事务类 AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); if ((Boolean) proxyTargetClass) { // 设置InfrastructureAdvisorAutoProxyCreator的proxyTargetClass属性为true AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); return; } } } } //...省略非关键代码 }
这里注册了一个bean: InfrastructureAdvisorAutoProxyCreator, 这个类继承了AbstractAdvisorAutoProxyCreator。
这个类的作用源码的注释已经告诉我们了:/** * Generic auto proxy creator that builds AOP proxies for specific beans * based on detected Advisors for each bean. * */
它是一个BeanPostProcessor,会在初始化后步骤中去寻找Advisor类型的Bean,并判断当前某个Bean是否有匹配的Advisor,是否需要利用动态代理产生一个代理对象。
-
ProxyTransactionManagementConfiguration
这个类是个配置类,又定义了3个bean:- BeanFactoryTransactionAttributeSourceAdvisor
- TransactionAttributeSource
- TransactionInterceptor
这三个bean相当于advisor,pointcut和advice。其作用就是判断类或者方法上是否存在@Transactional或@TransactionAttribute注解。 当类或者方法上有这两个注解时,生成代理类bean,调用该类或者方法时,会调用对应的代理对象
的方法, 最终执行TransactionInterceptor的invoke方法
这里注意,spring事务的基本原理就是代理,如果绕开代理直接调用类的方法事务是不会生效的
1.2 基本原理总结
- spring在启动时会启动扫描,添加了@EnableTransactionManagement的程序在bean的创建过程中会经过InfrastructureAdvisorAutoProxyCreator的初始化方法,判断当前Bean对象是否被BeanFactoryTransactionAttributeSourceAdvisor命中,命中对象继续判断类或方法上是否存在@Transactional或者@TransactionAttribute注解,如果存在则为该对象进行动态代理生成一个代理后的bean对象。
- 代理对象在执行方法时会再次判断当前执行的方法是否和BeanFactoryTransactionAttributeSourceAdvisor匹配,如果匹配则执行该Advisor中的TransactionInterceptor的invoke方法,执行代理事务流程:
- 利用配置的PlatformTransactionManager事务管理器创建一个数据库连接
- 修改数据库连接的auto commit为false
- 执行method Invocation.proceed(),将业务代码执行完毕
- 如果没有抛异常就提交
- 如果抛异常就回滚
2. 事务的传播机制
2.1 传播机制分类
这是事务中最灵活也是最容易出错的地方了,如果不理解这些传播机制,很容易导致生产事故,我们先来看看事务有哪些传播机制
事务传播行为类型 | 外部不存在事务 | 外部存在事务 | 说明 | 使用方式 |
---|---|---|---|---|
REQUIRED(默认) | 开启新的事务 | 融合到外部事务中 | 如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务 | 适用增删改查 |
SUPPORTS | 不开启新的事务 | 融合到外部事务中 | 当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行 | 适用查询 |
REQUIRES_NEW | 开启新的事务 | 不用外部事务,创建新的事务 | 创建一个新事务,如果存在当前事务,则挂起该事务。 | 适用内部事务和外部事务不存在业务关联情况,如日志 |
NOT_SUPPORTED | 不开启新的事务 | 不用外部事务 | 以非事务方式执行,如果当前存在事务,则挂起当前事务 | 不常用 |
NEVER | 不开启新的事务 | 抛出异常 | 不使用事务,如果当前事务存在,则抛出异常 | 不常用 |
MANDATORY | 抛出异常 | 融合到外部事务中 | 当前事务不存在就直接跑异常了,当前事务存在就在当前事务中执行 | 不常用 |
NESTED | 开启新的事务 | 融合到外部事务中,SavePoint机制,外层影响内层, 内层不会影响外层 | 如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样(开启一个事务) | 不常用 |
- NESTED和REQUIRES_NEW的区别
- REQUIRES_NEW是新开一个session,单独使用一个事务,这个事务与原有事务无关,原有事务回滚不会影响新开的事务,新开事务回滚原有事务也回滚。
- 而NESRTED是当前存在事务时开启一个嵌套事务,类似于父子关系,父子事务相互影响,父事务回滚时,子事务也会回滚,子事务回滚父事务同样回滚。
- NESTED和REQUIRED的区别
- REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一事务,那么被调用方出现异常时,由于共用一个事务,所以无论调用方是否catch其异常,事务都会回滚
- 而在NESTED情况下,被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不受影响
2.2 事务创建过程
spring将事务的创建过程封装的很好,开箱即用,但是作为高级开发一定要了解整个事务创建的过程,才能更好的理解事务,更快的定位问题
- 加了@Transactional注解的类或者方法,spring会生成对应的代理对象bean
- 执行代理对象方法的时候获取@Transactional中配置的传播类型
- 利用事务管理器创建一个数据库连接1
- 将数据库连接1的autocommit改为false
- 将数据库连接1设置到threadLocal中
- 执行方法中的sql
- 如果执行方法中调用了另一个代理对象的方法
- 获取@Transactional中配置的传播类型,判断是否需要挂起当前事务并创建新事物
- 在当前事务或新事物中执行完方法二业务
- 如果执行成功,新事物(如有)commit,挂起的事务重新运行执行完流程commit
- 如果执行失败,根据配置的传播类型新事物或挂起事务rollback
具体执行流程比较复杂,可以看下面的图
如果图片看不清,可以点击这里查看
3. 事务失效的场景
有的时候你在方法上加了@Transactional,觉得so easy,一个注解搞定,结果上生产的时候傻眼了,这什么情况,为什么我的业务失败了还是提交了一部分, 完全没有头绪啊QAQ。于是一直找是不是自己的业务代码有问题,结果半天都找不到,这个时候就该检查检查是不是自己配置的事务失效了。那么哪些情况下事务会失效呢
- 方法不是public的时候:
@Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。 - 发生自调用的时候:
如果你在当前类中直接调用本类的方法,此时相当于直接调用类的方法而不是spring生成的代理类方法,所以你调用的方法上无论配置怎样的@Transactional,都不会起效,例如下面这个类(userService.class):
解决方法也很简单:@Transactional public String insert() { userMapper.insert2(); error(); // 此时相当于调用一个普通方法,事务都是使用当前事务,不会去扫描判断error方法的传播类型 // 如果当前没有事务,无论error上配置什么都不会开启事务 // 所以这个requires_new不会起作用,error中的语句会回滚 return "请求成功"; } @Transactional(propagation = Propagation.REQUIRES_NEW) public void error() { userMapper.insert(); int i = 1 / 0; }
- 将当前方法移到另一个类中
- 将当前类注入到自身中,然后用注入的类调用目标方法,实例如下:
@Autowired private UserService userService; @Transactional public String insert() { userMapper.insert2(); // 调用本类代理类方法 userService.error(); return "请求成功"; } // 非事务方法调用本类的事务方法,事务就不生效了,解决方法:可以将当前事务方法移动到另外一个类中, // 或者在本类中注入本类,不用this调用,而是调用spring的代理对象 // 事务方法调用非事务方法事务生效 @Transactional(propagation = Propagation.REQUIRES_NEW) public void error() { userMapper.insert(); int i = 1 / 0; }
- 有可能当前数据库不支持事务
- 事务中抛出的异常被try-catch了,导致spring无法捕获异常,事务不回滚
- 当前类没有被spring管理
结语: 小小的一个注解包含的坑其实不少,执行流程非常复杂,希望本文能帮助你更好的认识spring,理解它用好它,觉得有帮助的点个赞吧,有时间会更新其他文章一起学习一起进步