Bootstrap

Java面试题总结(事物)

1、事物的四个隔离级别

严格意义上的事务实现应该是具备原子性、一致性、隔离性和持久性,简称 ACID。

  • 原子性(Atomicity),可以理解为一个事务内的所有操作要么都执行,要么都不执行。
  • 一致性(Consistency),可以理解为数据是满足完整性约束的,也就是不会存在中间状态的数据,比如你账上有400,我账上有100,你给我打200块,此时你账上的钱应该是200,我账上的钱应该是300,不会存在我账上钱加了,你账上钱没扣的中间状态
  • 隔离性(Isolation),指的是多个事务并发执行的时候不会互相干扰,即一个事务内部的数据对于其他事务来说是隔离的。
  • 持久性(Durability),指的是一个事务完成了之后数据就被永远保存下来,之后的其他操作或故障都不会对事务的结果产生影响。

事务的原理:基于AOP,如果一个类或者public方法上标注了@Transactional,Spring容器会在启动的时候为其创建一个代理类,然后在调用此方法的时候,实际调用的是TransactionInterceptor的invoke方法,这个方法的作用就是在目标方法之前开启事务,在方法执行过程中如果遇到异常则回滚事务,调用完成后提交事务。

2、事务传播行为:propagation

原理:所谓的事务中的传播行为其实就是当事务方法调其他方法时,被调用的方法可以通过@Transactional来决定如何应对调用方法的事务。

因此配置了@Transactional的方法会首先查询当前是否已经存在事务,熟悉JDBC的小伙伴都明白,事务是和数据库链接-Connection相关,所以无论Spring声明式事务外表多么华丽,繁杂,其本质还是基于Connection来完成,这点是毋庸置疑的,Spring也无法超脱于JDBC来另起炉灶。为了解决数据库链接跨方法传递,Spring使用了ThreadLocal来解决。
https://blog.csdn.net/m0_43448868/article/details/112115749

1、支持当前事务的情况

required:如果当前存在事务,则加入该事务,如果没有事务,则创建一个新的事务(默认配置是这个)

如何判断当前是否存在事务:在isExistingTransaction方法中就是通过判断传入的DataSourceTransactionObject对象(该对象由前面的doGetTransaction方法返回)持有的ConnectionHolder是否不等于空并且是否活跃。如果两个条件都满足则返回true,否则false。

如何开启一个新事务:重新获取一个数据库链接

supports:如果当前存在事务,则加入事务,如果当前没有事务,则以非事务的方式继续运行

mandatory(强制):如果当前存在事务,则接入该事务,如果当前没有事务,则抛出异常

2、不支持当前事务的情况

requires_new:创建一个新的事务,如果当前存在事务,则把当前事务挂起

挂起事务其实就是先从六个ThreadLocal中获取到当前事务的定义信息,并将这六个ThreadLocal重置为初始状态,根据当前事务的定义信息创建SuspendedResourcesHolder并返回。

not_supported:以非事务方式运行,如果当前存在事务,则把当前事务挂起

propagation_never:以非事务方式运行,如果当前存在任务,则抛出异常

3、其他情况

nested:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行,如果当前没有事务,则等价于required

适用情况:事务1方法里面有个方法定义了事务2,如果想要事务2方法异常时回滚,同时不影响事务1执行,则可以采用nested嵌套事务模式

3、事务不生效的原因

1、注解@Transactional配置的方法非public权限修饰 

@Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。

2、注解@Transactional所在类不是Spring容器管理的bean(比如说没有@Service)

3、注解@Transactional的类中,注解修饰的方法被同一个类下的内部其他方法调用

这种失效场景是我们日常开发中最常踩坑的地方;在类A里面有方法a 和方法b, 然后方法b上面用 @Transactional加了方法级别的事务,在方法a里面 调用了方法b, 方法b里面的事务不会生效。
为什么会失效呢?:其实原因很简单,Spring在扫描Bean的时候会自动为标注了@Transactional注解的类生成一个代理类(proxy),当有注解的方法被调用的时候,实际上是代理类调用的,代理类在调用之前会开启事务,执行事务的操作,但是同类中的方法互相调用,相当于this.B(),此时的B方法并非是代理类调用,而是直接通过原有的Bean直接调用,所以注解会失效。
解决方法:类内部使用其代理类调用事务方法  ((ClassServiceImpl)AopContext.currentProxy()).insertClassByException(classDo);

需要指定aspectj  <aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/> 否则会报

Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.异常

或者自己@autowire自己,或者通过SpringUtils.getBean获取service的实例,然后再调用事务方法

https://blog.csdn.net/hellozhxy/article/details/109753711

4、异常被try catch捕获,并且catch块中没有抛出异常(事务只有在抛出异常的时候才会回滚)。同时 如果不配置rollbackfor,则只有在碰到RuntimeException或者error时才会回滚,  解决方法:@Transactional注解修饰的方法,加上rollbackfor属性值,指定回滚异常类型:

@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)

5、存储引擎是MyISAM,则事务不生效,因为此引擎不支持事务,需要设置为innodb

3、分布式事务

二段提交 2PC 2PC(Two-phase commit protocol),中文叫二阶段提交。 二阶段提交是一种强一致性设计

是一个同步阻塞协议,一阶段,协调者会等待所有参与者响应才会进行下一步操作,同时协调者有超时机制

协调者故障问题:可以通过选举得到新协调者,同时通过记录日志,使协调者之前相互通知

3PC 

3PC 包含了三个阶段,分别是准备阶段、预提交阶段和提交阶段,对应的英文就是:CanCommit、PreCommit 和 DoCommit

 

;