1. 你在项目中是如何使用事物的?
我们项目的框架都是使用的Spring,spring分为 编程式事务,在代码中硬编码。声明式事务,在配置文件中配置(推荐使用)
声明式事务又分为两种:基于XML的声明式事务基于注解的声明式事务。我一般都是通过注解来进行的事务控制。也就是@Transactional
2. 先简单介绍一下@Transactional注解吗?项目中如何使用的?有哪些注意点吗?
我们都是把注解加到需要使用事务控制的方法上,也可以加到类上,加到类上是给类里的所有的方法都加了事务,不建议这样做,这样会增加不需要使用事务的接口的响应时长。
@Transactional注解只能用在public 方法上,如果用在protected或者private的方法上,不会报错,但是该注解不会生效。
@Transactional注解只能回滚非检查型异常,具体为RuntimeException及其子类。
3. Spring 事务中的隔离级别有哪几种?
TransactionDefinition 接口中定义了五个表示隔离级别的常量:
TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,
Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别。
TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取未提交的数据变更,可能会导致脏读、幻读或不可重复读。
TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
4. Spring 事务中哪几种事务传播行为?
支持当前事务的情况:
TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;
如果当前没有事务,则创建一个新的事务。
TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;
如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)不支持当前事务的情况:
不支持当前事务的情况:
TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,
如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,
如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,
如果当前存在事务,则抛出异常。
其他情况:
TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
5. @Transactional注解只能用在public 方法上,这是为什么?
Spring事务的实现都是依靠AOP,本质上也是依靠代理来实现。事务在spring中的实现其实就是生成bean对象的代理对象。
在bean进行创建出实例时, 如果是有事务注解的方法,就会被进行增强,最终形成代理类。在spring中,有两种动态代理的方式,一种是jdk,它是将原始对象放入代理对象内部,通过调用内含的原始对象来实现原始的业务逻辑,而另一种是cglib,它是通过生成原始对象的子类,子类复写父类的方法,从而实现对父类的增强。
jdk中,如果是private的方法,显然是无法访问的,而在cglib中,也是同样。所以总结来说private方法不能被继承,final方法不能被重写,static方法和继承不相干,所以它们3个的事务不起作用。
Spring选择让protected方法和package方法不支持事务,所以只有public方法支持事务
6. 一个类中没加事务的方法调用加事务的方法,为什么事务失效?怎么解决Spring事务失效的问题?
原因:
Spring事务管理用的是AOP,AOP底层用的是动态代理。所以spring 在扫描bean的时候会扫描方法上是否包含@Transactional注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用之前就会启动transaction。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,所以就不会启动transaction。
解决方式:
- 把方法B抽离到另外一个XXService中去,并且在这个Service中注入XXService,使用XXService调用方法B;
- 通过在方法内部获得当前类代理对象的方式,通过代理对象调用方法B
上面说了:动态代理最终都是要调用原始对象的,而原始对象在去调用方法时,是不会再触发代理了!所以我们就使用代理对象来调用,就会触发事务;
综上解决方案,那怎么获取代理对象呢? 这里提供两种方式:
- 使用 ApplicationContext 上下文对象获取该对象;
- 使用 AopContext.currentProxy() 获取代理对象,但是需要配置exposeProxy=true
7. 同一个类中标有@Transactional 的方法A,调用另一个标有@Transactional的 方法B会开启几个事务?
一个事务
Spring事务管理用的是AOP,AOP底层用的是动态代理。所以spring 在扫描bean的时候会扫描方法上是否包含@Transactional注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。
此时,当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用之前就会启动transaction。如果是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,所以就不会启动transaction,我们说只有一个事务。
8. 那么如何才能让上面两个方法开启两个事务呢?
1.把方法B抽离到另外一个XXService中去,在这个Service中注入XXService,使用XXService调用方法B;
2.通过在方法内部获得当前类代理对象的方式,通过代理对象调用方法B
上面说了:动态代理最终都是要调用原始对象的,而原始对象在去调用方法时,是不会再触发代理了!所以我们就使用代理对象来调用,就会触发事务;
综上解决方案,那怎么获取代理对象呢? 这里提供两种方式:
1.使用 ApplicationContext 上下文对象获取该对象;
2.使用 AopContext.currentProxy() 获取代理对象,但是需要配置exposeProxy=true
TestService testService = (TestService)AopContext.currentProxy();
testService.B();
同时还需要在B方法将传播行为配置为 @Transactional(propagation = Propagation.REQUIRES_NEW)
9. @Transactional实现原理
注解介绍
@Transactional是spring中声明式事务管理的注解配置方式。@Transactional注解可以帮助我们把事务开启、提交或者回滚的操作,通过aop的方式进行管理。
通过@Transactional注解就能让spring为我们管理事务,免去了重复的事务管理逻辑,减少对业务代码的侵入,使我们开发人员能够专注于业务层面开发。
我们知道实现@Transactional原理是基于spring aop,aop又是动态代理模式的实现,下面我们就详细分析一下实现原理
实现原理
猜想
- 首先,对于spring中aop实现原理有了解的话,应该知道想要对一个方法进行代理的话,肯定需要定义切点。在@Transactional的实现中,同样如此,spring为我们定义了以 @Transactional 注解为植入点的切点,这样才能知道@Transactional注解标注的方法需要被代理。
- 有了切面定义之后,在spring的bean的初始化过程中,就需要对实例化的bean进行代理,并且生成代理对象。
- 生成代理对象的代理逻辑中,进行方法调用时,需要先获取切面逻辑,@Transactional注解的切面逻辑类似于@Around,在spring中是实现一种类似代理逻辑。
源码分析
- 在配置好注解驱动方式的事务管理之后,spring会在ioc容器创建一个BeanFactoryTransactionAttributeSourceAdvisor实例,这个实例可以看作是一个切点,在判断一个bean在初始化过程中是否需要创建代理对象,都需要验证一次BeanFactoryTransactionAttributeSourceAdvisor是否是适用这个bean的切点。如果是,就需要创建代理对象,并且把BeanFactoryTransactionAttributeSourceAdvisor实例注入到代理对象中。
- 分析代理的对象发现,最终的代理对象的代理方法是DynamicAdvisedInterceptor#intercept,分析这个方法后发现他最终调用的是TransactionInterceptor#invoke方法,并且把CglibMethodInvocation注入到invoke方法中,从上面可以看到CglibMethodInvocation是包装了目标对象的方法调用的所有必须信息,因此,在TransactionInterceptor#invoke里面也是可以调用目标方法的,并且还可以实现类似@Around的逻辑,在目标方法调用前后继续注入一些其他逻辑,比如事务管理逻辑。
- 当我们调进去TransactionInterceptor#invoke方法发现其中的核心方法是invokeWithinTransaction。