@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
注解具备以下作用:
看完之后是不是觉得内心毫无波澜,甚至有点不知所以然?诶,我为什么要把这些属性罗列出来呢?并且还说了一堆废话。
O,我的兄弟们呐,既然@Transactional
注解上给出了这些属性并且支持我们修改他们的值来改变注解的行为策略,那它在源码里面肯定要支持的呀。我知道不少人看完我这句话会有这种想法:这…,听君一席话入听一席话呀!这个我也知道啊,还要你说吗。那我只能说:如果你已经悟了这一点,说明你不是我的目标提醒群体。
我相信,肯定也有不少朋友一脸懵逼的进来,最后也没抓住这个源码解析的重点。知道了上述属性的作用,对我们阅读源码其实是有大大的帮助的。还是那句话:通过业务阅读源码,远比通过源码洞悉业务简单得多!
好啦,言归正传。在上面的属性中,其中尤为重要的,当是propagation
属性,事务的传播类型/策略。默认为Propagation.REQUIRED
,另外还有一些其他属性,分别表现为不一样的策略。下面我们再进一步简单介绍下
NESTED
会在执行方法前设置一个safePoint
安全点,当出现回滚的时候,会回滚到当前安全点,而不是整体回滚源码如下:
@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,表示若当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。所以上面这种情况的执行流程如下:
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......
}
}
事务分析:所以上面这种情况的执行流程如下:
假如是这种情况:
@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;
}
}
事务分析:所以上面这种情况的执行流程如下:
如果是这种情况:
@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;
}
}
事务分析:所以上面这种情况的执行流程如下:
a()
没有捕获异常,所以在这里会继续抛出异常给到方法test
,紧接着回滚我们在前置知识里面简单的给大家演示了几个我们在工作中比较常用的@Tranactional场景,并且也简单的做了事务流程分析。但显然,Spring事务也不会仅仅只是做了这些而已。那么,更具体、更详细的过程是怎样的呢?Spring事务传播机制又是如何在代码中体现的呢?
在开发过程中,经常会出现一个方法调用另外一个方法,那么这里就涉及到了多种场景,比如a()
调用b()
,那么我们可能需要考虑如下情况:
a()
和b()
方法中的所有sql需要在同一个事务中吗?a()
和b()
方法需要单独的事务吗?a()
需要在事务中执行,b()
还需要在事务中执行吗?所以,这就要求Spring事务能支持上面各种场景,这就是Spring事务传播机制的由来。那Spring事务传播机制是如何实现的呢?
我们来看看上述场景中第二种情况,a()
在一个事务中执行,调用b()
方法时需要新开一个事务执行时的,分别的场景是怎样的:
a()
方法前,先利用事务管理器新建一个数据库连接aautocommit
改为falseThreadLocal
中(这一步没有经验的朋友也许没想到,或者比较陌生。简单来说,就是为了保证当前线程内所有的方法用的数据库连接是同一个。如果连接都不一样,那事务如何生效?)a()
方法中的sqla()
方法过程中,调用了b()
方法(注意用代理对象调用b()方法)
b()
方法前,判断出来了当前线程中已经存在一个数据库连接a了(用上面的ThreadLocal
可以判断出来),表示当前线程其实已经拥有一个Spring事务了,则进行挂起ThreadLocal
中的数据库连接a从ThreadLocal
中移除,并放入一个挂起资源对象中(下面会解释)autocommit
改为falseThreadLocal
中b()
方法正常执行完,则从ThreadLocal
中拿到数据库连接b进行提交ThreadLocal
中a()
方法正常执行完,则从ThreadLocal
中拿到数据库连接a进行提交上面简单描述了开启事务的,以及在Propagation.REQUIRES_NEW
事务传播类型下,单独新开事务的过程。这算是一个比较典型的事务过程了,我们也会在这个基础上,大概的讲述一下源码流程。
在Spring中,如果我们想要开启事务的话,需要使用这个注解,就好比是使用AOP需要使用@EnableAspectJAutoProxy一样。这个开启Spring事务注解,本质上就是增加了一个Advisor,该注解代理的功能就是向Spring容器中添加了两个Bean:AutoProxyRegistrar
和ProxyTransactionManagementConfiguration
。
AutoProxyRegistrar
:AutoProxyRegistrar主要的作用是向Spring容器中注册了一个InfrastructureAdvisorAutoProxyCreator
的Bean。而InfrastructureAdvisorAutoProxyCreator
继承了AbstractAdvisorAutoProxyCreator
,所以这个类的主要作用就是开启自动代理的作用,也就是一个BeanPostProcessor,会在初始化后步骤中去寻找Aspect并且解析Advisor类型的Bean,并判断当前某个Bean是否有匹配的Advisor,是否需要利用动态代理产生一个代理对象。BeanFactoryTransactionAttributeSourceAdvisor
:一个Advisor。所以里面会包含匹配策略PointCut,以及真正执行的切入方法通知AdviceAnnotationTransactionAttributeSource
:相当于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
中完成!