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
。