Spring 事务 @Transactional注解 面试及原理

开发 前端
我们都是把注解加到需要使用事务控制的方法上,也可以加到类上,加到类上是给类里的所有的方法都加了事务,不建议这样做,这样会增加不需要使用事务的接口的响应时长。

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。

责任编辑:武晓燕 来源: 今日头条
相关推荐

2021-06-26 14:59:13

SpringTransaction执行

2023-11-02 07:52:30

Java工具

2023-09-27 16:22:51

SpringMySQL原子性

2021-04-14 15:17:08

Transaction代码语言

2023-09-28 09:07:54

注解失效场景

2022-09-20 22:27:08

事务失效public 修饰

2022-09-14 19:50:22

事务场景流程

2023-04-02 13:57:04

Java自定义事务管理器

2022-08-08 17:38:45

Spring策略事务

2022-08-09 09:34:32

Spring开发

2023-08-31 08:12:23

应用场景业务异常HTTP

2021-03-10 10:55:51

SpringJava代码

2021-11-29 06:57:50

并行Stream Spring

2020-11-17 08:28:55

数据库

2020-10-19 11:05:17

SpringTransaction事务

2018-11-16 15:35:10

Spring事务Java

2020-08-19 09:45:29

Spring数据库代码

2022-02-14 16:53:57

Spring项目数据库

2023-09-08 08:52:12

Spring注解事务

2023-08-29 10:51:44

点赞
收藏

51CTO技术栈公众号