【Spring专题】Spring之事务底层源码解析

前置知识

@Transactional简单介绍

@Transactional 注解相信大家并不陌生,平时开发中很常用的一个注解。它可以作用在类上面,所有该类的public方法都配置相同的事务属性信息;也可以作用在方法上面,表示方法被纳入了事务管理钟。被事务管理的方法,能保证方法内多个数据库操作要么同时成功、要么同时失败。但是使用@Transactional注解时又需要注意一些细节,不然一个不小心就事务失效了。
下面@Transactional注解的源码,我们来看看它都有什么属性:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

    /**
     * 同下面的transactionManager,用来确定目标的事务管理器的名称
     */
    @AliasFor("transactionManager")
    String value() default "";

    /**
     * 同上面的value,用来确定目标的事务管理器的名称
     */
    String transactionManager() default "";
    
    /**
     * 事务传播类型/策略。默认值为 Propagation.REQUIRED
     */
    Propagation propagation() default Propagation.REQUIRED;

    /**
     * 事务的隔离级别,默认值为 Isolation.DEFAULT
     */
    Isolation isolation() default Isolation.DEFAULT;

    /**
     * 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
     */
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

    /**
     * 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
     */
    boolean readOnly() default false;

    /**
     * 定义零(0)个或多个异常类,这些异常类必须是Throwable的子类,指示哪些异常类型必须引起事务回滚。
     * 默认情况下,事务将在RuntimeException和Error上回滚,但不会在检查异常(业务异常)上回滚.
     * 这是构造回滚规则的首选方法(与rollbackForClassName相反),匹配异常类型、它的子类和它的嵌套类。
     */
    Class<? extends Throwable>[] rollbackFor() default {};

    /**
     * 定义零(0)个或多个异常类,这些异常类必须是Throwable的子类,指示哪些异常类型不能导致事务回滚。
     * 这是构造回滚规则的首选方法(与noRollbackForClassName相反),匹配异常类型、它的子类和它的嵌套类。
     */
    Class<? extends Throwable>[] noRollbackFor() default {};
}

上面贴的基本上是我们在业务代码中能使用到的关于@Transactional注解的了。通过属性我们可以看出,@Transactional注解具备以下作用:

  1. 指定目标事务管理器
  2. 指定事务的传播类型
  3. 指定事务的隔离级别
  4. 指定事务的超时时间
  5. 指定事务是否为只读事务
  6. 指示哪些异常类型能导致事务回滚
  7. 指示哪些异常类型不能导致事务回滚

看完之后是不是觉得内心毫无波澜,甚至有点不知所以然?诶,我为什么要把这些属性罗列出来呢?并且还说了一堆废话。
【Spring专题】Spring之事务底层源码解析_第1张图片
O,我的兄弟们呐,既然@Transactional注解上给出了这些属性并且支持我们修改他们的值来改变注解的行为策略,那它在源码里面肯定要支持的呀。我知道不少人看完我这句话会有这种想法:这…,听君一席话入听一席话呀!这个我也知道啊,还要你说吗。那我只能说:如果你已经悟了这一点,说明你不是我的目标提醒群体
我相信,肯定也有不少朋友一脸懵逼的进来,最后也没抓住这个源码解析的重点。知道了上述属性的作用,对我们阅读源码其实是有大大的帮助的。还是那句话:通过业务阅读源码,远比通过源码洞悉业务简单得多!

好啦,言归正传。在上面的属性中,其中尤为重要的,当是propagation属性,事务的传播类型/策略。默认为Propagation.REQUIRED,另外还有一些其他属性,分别表现为不一样的策略。下面我们再进一步简单介绍下

Spring事务传播类型

  • Propagation.REQUIRED:直译:必须的。默认的策略。表示若当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务
  • Propagation.SUPPORTS:直译:支持的。表示若当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行
  • Propagation.MANDATORY:直译:强制的(强制需要事务)。表示若当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常
  • Propagation.REQUIRES_NEW:直译:新建的。重新创建一个新的事务,如果当前存在事务,暂停当前的事务
  • Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务
  • Propagation.NEVER:直译:从来不。以非事务的方式运行,如果当前存在事务,则抛出异常
  • Propagation.NESTED:直译:嵌套的。它整体上和 Propagation.REQUIRED 效果一样,但是当出现异常的时候不一样。NESTED会在执行方法前设置一个safePoint安全点,当出现回滚的时候,会回滚到当前安全点,而不是整体回滚

@Transactional使用示例

示例一:

源码如下:

@Component
public class UserService {
	@Autowired
	private UserService userService;
	
	@Transactional
	public void test() {
		 // test方法中的sql:insert......
		 userService.a();
	}
	
	@Transactional
	public void a() {
		 // a方法中的sql:insert......
	}
}

事务分析:默认情况下传播机制为REQUIRED,表示若当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。所以上面这种情况的执行流程如下:

  1. 新建一个数据库连接conn
  2. 设置conn的autocommit为false
  3. 执行test方法中的sql
  4. 执行a方法中的sql
  5. 执行conn的commit()方法进行提交

PS:大家有没有注意到上面的一个细节?UserService 类下面,有一个属性UserService ,自己依赖自己。然后再test方法里面,这样来调用userService.a();方法。为嘛啊?因为啊,当方法加上@Transactional注解之后,这个类就被代理了。如果此时,我们在test()中这样调用:

@Transactional
	public void test() {
		 // test方法中的sql:insert......
		 this.a();
	}

那仅仅是普通的Java方法a()而已,他是不会去处理你a()上面的@Transactional的。不知道大家理解不?但是通过自己依赖自己,Spring就会帮我们找到自己的代理对象,等于我还是用的代理对象去调用方法a()了。

示例二

假如是这种情况:

@Component
public class UserService {
	@Autowired
	private UserService userService;
	
	@Transactional
	public void test() {
		 // test方法中的sql:insert......
		 userService.a();
	     int result = 100/0;
	}
	
	@Transactional
	public void a() {
	 	// a方法中的sql:insert......
	}
}

事务分析:所以上面这种情况的执行流程如下:

  1. 新建一个数据库连接conn
  2. 设置conn的autocommit为false
  3. 执行test方法中的sql
  4. 执行a方法,中的sql
  5. 抛出异常
  6. 执行conn的rollback()方法进行回滚,所以两个方法中的sql都会回滚掉

示例三

假如是这种情况:

@Component
public class UserService {
	 @Autowired
	 private UserService userService;
	
	 @Transactional
	 public void test() {
		  // test方法中的sql
		  userService.a();
	 }
	
	 @Transactional
	 public void a() {
	  	 // a方法中的sql
	     int result = 100/0;
	 }
}

事务分析:所以上面这种情况的执行流程如下:

  1. 新建一个数据库连接conn
  2. 设置conn的autocommit为false
  3. 执行test方法中的sql
  4. 执行a方法中的sql
  5. 抛出异常
  6. 执行conn的rollback()方法进行回滚,所以两个方法中的sql都会回滚掉

示例四

如果是这种情况:

@Component
public class UserService {
	@Autowired
	private UserService userService;
	
	@Transactional
	public void test() {
		// test方法中的sql
		userService.a();
	}
	
	@Transactional(propagation = Propagation.REQUIRES_NEW)
	public void a() {
		// a方法中的sql
		int result = 100/0;
	}
}

事务分析:所以上面这种情况的执行流程如下:

  1. 新建一个数据库连接conn
  2. 设置conn的autocommit为false
  3. 执行test方法中的sql
  4. 又新建一个数据库连接conn2
  5. 执行a方法中的sql
  6. 抛出异常
  7. 执行conn2的rollback()方法进行回滚
  8. 由于调用方法a()没有捕获异常,所以在这里会继续抛出异常给到方法test,紧接着回滚
  9. 执行conn的rollback()方法进行回滚,最终还是两个方法中的sql都回滚了

课程内容

一、Spring之事务

1.1 简单回顾

我们在前置知识里面简单的给大家演示了几个我们在工作中比较常用的@Tranactional场景,并且也简单的做了事务流程分析。但显然,Spring事务也不会仅仅只是做了这些而已。那么,更具体、更详细的过程是怎样的呢?Spring事务传播机制又是如何在代码中体现的呢?
在开发过程中,经常会出现一个方法调用另外一个方法,那么这里就涉及到了多种场景,比如a()调用b(),那么我们可能需要考虑如下情况:

  1. a()b()方法中的所有sql需要在同一个事务中吗?
  2. a()b()方法需要单独的事务吗?
  3. a()需要在事务中执行,b()还需要在事务中执行吗?
  4. 等等情况…

所以,这就要求Spring事务能支持上面各种场景,这就是Spring事务传播机制的由来。那Spring事务传播机制是如何实现的呢?
我们来看看上述场景中第二种情况,a()在一个事务中执行,调用b()方法时需要新开一个事务执行时的,分别的场景是怎样的:

  1. 首先,代理对象执行a()方法前,先利用事务管理器新建一个数据库连接a
  2. 将数据库连接a的autocommit改为false
  3. 把数据库连接a设置到ThreadLocal中(这一步没有经验的朋友也许没想到,或者比较陌生。简单来说,就是为了保证当前线程内所有的方法用的数据库连接是同一个。如果连接都不一样,那事务如何生效?)
  4. 执行a()方法中的sql
  5. 执行a()方法过程中,调用了b()方法(注意用代理对象调用b()方法)
    • 代理对象执行b()方法前,判断出来了当前线程中已经存在一个数据库连接a了(用上面的ThreadLocal可以判断出来),表示当前线程其实已经拥有一个Spring事务了,则进行挂起
    • 挂起就是把ThreadLocal中的数据库连接a从ThreadLocal中移除,并放入一个挂起资源对象中(下面会解释)
    • 挂起完成后,再次利用事务管理器新建一个数据库连接b
    • 将数据库连接b的autocommit改为false
    • 把数据库连接b设置到ThreadLocal
    • 执行b()方法中的sql
    • b()方法正常执行完,则从ThreadLocal中拿到数据库连接b进行提交
    • 提交之后会恢复所挂起的数据库连接a,这里的恢复,其实只是把在挂起资源对象中所保存的数据库连接a再次设置到ThreadLocal
  6. a()方法正常执行完,则从ThreadLocal中拿到数据库连接a进行提交

上面简单描述了开启事务的,以及在Propagation.REQUIRES_NEW事务传播类型下,单独新开事务的过程。这算是一个比较典型的事务过程了,我们也会在这个基础上,大概的讲述一下源码流程。

1.2 概念回顾

1.3 核心方法讲解

二、方法讲解

2.1 整体流程图

2.2 先讲@EnableTransactionManagement

在Spring中,如果我们想要开启事务的话,需要使用这个注解,就好比是使用AOP需要使用@EnableAspectJAutoProxy一样。这个开启Spring事务注解,本质上就是增加了一个Advisor,该注解代理的功能就是向Spring容器中添加了两个Bean:AutoProxyRegistrarProxyTransactionManagementConfiguration

  • AutoProxyRegistrar:AutoProxyRegistrar主要的作用是向Spring容器中注册了一个InfrastructureAdvisorAutoProxyCreator的Bean。而InfrastructureAdvisorAutoProxyCreator继承了AbstractAdvisorAutoProxyCreator,所以这个类的主要作用就是开启自动代理的作用,也就是一个BeanPostProcessor,会在初始化后步骤中去寻找Aspect并且解析Advisor类型的Bean,并判断当前某个Bean是否有匹配的Advisor,是否需要利用动态代理产生一个代理对象。
  • ProxyTransactionManagementConfiguration是一个配置类,它又定义了另外三个bean:
    • BeanFactoryTransactionAttributeSourceAdvisor:一个Advisor。所以里面会包含匹配策略PointCut,以及真正执行的切入方法通知Advice
    • AnnotationTransactionAttributeSource:相当于BeanFactoryTransactionAttributeSourceAdvisor中的Pointcut。其就是用来判断某个类上是否存在@Transactional注解,或者判断某个方法上是否存在@Transactional注解的(其实仔细想想就好了。我们正常使用的pointcut是一段匹配表达式,但其实在事务里面没那么复杂,某个类或方法上面有@Transactional注解就是我们要找的目标了)
    • TransactionInterceptor:相当于BeanFactoryTransactionAttributeSourceAdvisor中的Advice。它是一个MethodInterceptor的子类(我们在前面的AOP讲过,其实所有的Advice都是,或者说可以是MethodInterceptor。在解析的过程中会把所有的Advice封装成MethodInterceptor的),当某个类中存在@Transactional注解时,到时就产生一个代理对象作为Bean,代理对象在执行某个方法时,最终就会进入到TransactionInterceptor的invoke()方法。

O,如果大家了解AOP或者看过我前面的笔记的化,对上面提到的这三个概念并不会感到很陌生。我们要研究的类无非就两个:AnnotationTransactionAttributeSource是如何扮演pointCut是使用何种策略匹配切点,而TransactionInterceptor又是如何实现代理加强方法的呢?很显然,我们在上面【简单回顾】提到的流程,都将会在TransactionInterceptor中完成!

学习总结

你可能感兴趣的:(tuling学院学习笔记,spring,数据库,java)