一文搞懂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,理解它用好它,觉得有帮助的点个赞吧,有时间会更新其他文章一起学习一起进步

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值