浅谈 @Transtational 原理 及其失效场景(1)

上篇讲述了 当@Transtctional遇到@Async遇碰撞出怎样的火花? 本篇则主要从@Transtational出发仔细谈谈。

一、调用场景复现,代码层面什么情况会失效?

1)同类操作:事物A调取非事物B,A报错/B报错(事物生效)

@Transactional(rollbackFor = Exception.class)
	@Override
	public void saveInspectInfoA() {
		//事物操作方法1
		taskInspectMapper.updateDataStatus(126387703699750912L);
		this.saveInspectInfoB();
		//回滚
		TaskInspect taskInspect = new TaskInspect();
		taskInspect.setProject("sss");
		//事物操作方法2
		taskInspectMapper.insertSelective(taskInspect);
	}

	public void saveInspectInfoB() {
		//事物操作方法3
		taskInspectMapper.updateDataStatus(126389155885236224L);
	}

在这里插入图片描述
结果:回滚

@Transactional(rollbackFor = Exception.class)
	@Override
	public void saveInspectInfoA() {
		//事物操作方法1
		taskInspectMapper.updateDataStatus(126387703699750912L);
		this.saveInspectInfoB();

	}

	public void saveInspectInfoB() {
		//事物操作方法3
		taskInspectMapper.updateDataStatus(126389155885236224L);
		//回滚
		TaskInspect taskInspect = new TaskInspect();
		taskInspect.setProject("sss");
		//事物操作方法2
		taskInspectMapper.insertSelective(taskInspect);
	}

结果:回滚 和代码顺序无关

2)同类操作:非事物A调取事物B,A报错/B报错(事物失效)

2-1)报错逻辑放最后一层

@Override
	public void saveInspectInfoA() {
		//事物操作方法1
		taskInspectMapper.updateDataStatus(126387703699750912L);
		this.saveInspectInfoB();

	}

	@Transactional(rollbackFor = Exception.class)
	public void saveInspectInfoB() {
		//事物操作方法3
		taskInspectMapper.updateDataStatus(126389155885236224L);
		//回滚
		TaskInspect taskInspect = new TaskInspect();
		taskInspect.setProject("sss");
		//事物操作方法2
		taskInspectMapper.insertSelective(taskInspect);
	}

结果:由于报错在最后,所以都执行成功;

2-2)报错逻辑放中间

@Override
	public void saveInspectInfoA() {
		//事物操作方法1
		taskInspectMapper.updateDataStatus(126387703699750912L);
		this.saveInspectInfoB();

	}

	@Transactional(rollbackFor = Exception.class)
	public void saveInspectInfoB() {
		//回滚
		TaskInspect taskInspect = new TaskInspect();
		taskInspect.setProject("sss");
		//事物操作方法2
		taskInspectMapper.insertSelective(taskInspect);
		//事物操作方法3
		taskInspectMapper.updateDataStatus(126389155885236224L);
	}

在这里插入图片描述
结果:报错之前的都执行成功;之后的都未执行;

这种情况则是最常用的一种失效的场景。

3)非同类操作:事物A调取非事物B,A报错/B报错(事物生效)

@Override
	@Transactional(rollbackFor = Exception.class)
	public void saveInspectInfoA() {
		//事物操作方法1
		taskInspectMapper.updateDataStatus(126387703699750912L);
		asyncTestService.saveInspectInfoB();
	}
public void saveInspectInfoB() {
		//事物操作方法3
		taskInspectMapper.updateDataStatus(126389155885236224L);
		//回滚
		TaskInspect taskInspect = new TaskInspect();
		taskInspect.setProject("sss");
		//事物操作方法2
		taskInspectMapper.insertSelective(taskInspect);
	}

结果:回滚 和代码顺序无关

@Override
	@Transactional(rollbackFor = Exception.class)
	public void saveInspectInfoA() {
		//事物操作方法1
		taskInspectMapper.updateDataStatus(126387703699750912L);
		asyncTestService.saveInspectInfoB();
		//回滚
		TaskInspect taskInspect = new TaskInspect();
		taskInspect.setProject("sss");
		//事物操作方法2
		taskInspectMapper.insertSelective(taskInspect);
	}
public void saveInspectInfoB() {
		//事物操作方法3
		taskInspectMapper.updateDataStatus(126389155885236224L);
	}

结果:回滚 和代码顺序无关

4)非同类操作:非事物A调取事物B,A报错

@Override
	public void saveInspectInfoA() {
		//事物操作方法1
		taskInspectMapper.updateDataStatus(126387703699750912L);
		//回滚
		TaskInspect taskInspect = new TaskInspect();
		taskInspect.setProject("sss");
		//事物操作方法2
		taskInspectMapper.insertSelective(taskInspect);
		asyncTestService.saveInspectInfoB();
	}
@Transactional(rollbackFor = Exception.class)
	public void saveInspectInfoB() {
		//事物操作方法3
		taskInspectMapper.updateDataStatus(126389155885236224L);
	}

事物只对B里的代码有效,A毫无关系,只是一个单纯的调用者。
这种使用情况是有问题的,更新插入操作一起才能有事物一致性的概念。

5)非同类操作:非事物A调取事物B,B报错

@Override
	public void saveInspectInfoA() {
		//事物操作方法1
		taskInspectMapper.updateDataStatus(126387703699750912L);
		asyncTestService.saveInspectInfoB();
	}
@Transactional(rollbackFor = Exception.class)
	public void saveInspectInfoB() {
		//回滚
		TaskInspect taskInspect = new TaskInspect();
		taskInspect.setProject("sss");
		//事物操作方法2
		taskInspectMapper.insertSelective(taskInspect);
		//事物操作方法3
		taskInspectMapper.updateDataStatus(126389155885236224L);
	}

B代码失败,对应的B方法里的回滚,A不影响。

二、调用场景复现,代码层面 话语总结

我们通过四种情况发现:

1)事物A调取非事物B,不在乎B方法在同类还是非同类,反正其代码是附属于A的,所以这种情况 事物都是生效的。

2)非事物A调取事物B,B必须在非同类里,非事物A则主要是写一些查询类的逻辑,涉及到数据库的,必须保证是在方法B中。

3)事物B中再次调取异步方法除外,无关紧要或者用实物同步器实现。

三、源码解析,到底是如何回滚的?

1)@Transactional 注解

@java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD})
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Inherited
@java.lang.annotation.Documented
public @interface Transactional {
	//别名,和transactionManager一样
    @org.springframework.core.annotation.AliasFor("transactionManager")
    java.lang.String value() default "";
	//事务管理器,Spring事务支持使用多个事务管理器,可以通过该属性使用指定事务管理器
    @org.springframework.core.annotation.AliasFor("value")
    java.lang.String transactionManager() default "";

    java.lang.String[] label() default {};
    //事物传播方式 默认为:Propagation.REQUIRED(支持当前事务,如果当前没有事务,就新建一个事务)
    org.springframework.transaction.annotation.Propagation propagation() default org.springframework.transaction.annotation.Propagation.REQUIRED;
	//事务的隔离级别,默认值是 Isolation.DEFAULT,即数据库设定的隔离级别
    org.springframework.transaction.annotation.Isolation isolation() default org.springframework.transaction.annotation.Isolation.DEFAULT;

    int timeout() default -1;
	//事务超时时间,默认为数据库设定的时间
    java.lang.String timeoutString() default "";
	//事物可读
    boolean readOnly() default false;
    //设定要回滚事务的异常类,当捕获到这些异常时回滚,否则不回滚
    java.lang.Class<? extends java.lang.Throwable>[] rollbackFor() default {};
	//设定要回滚事务的异常类名称,当捕获到这些异常时回滚,否则不回滚。
    java.lang.String[] rollbackForClassName() default {};
	//设定不回滚事务的异常类,当捕获到这些异常时不回滚
    java.lang.Class<? extends java.lang.Throwable>[] noRollbackFor() default {};
	//设定不回滚事务的异常类名称,当捕获到这些异常时不回滚
    java.lang.String[] noRollbackForClassName() default {};
}

@java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD}) :表明注解可以应用于方法和实体类上。
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME):表明应该在运行时保留。

2)事物传播方式 Propagation

传播方式说明
REQUIRED当前有事务时沿用现有事务,无事物则新建一个事物
SUPPORTS当前有事务时沿用现有事务,无事物则不使用事务
MANDATORY当前有事务时沿用现有事务,当前有事务时沿用现有事务
REQUIRES_NEW当前有事物,挂起;创建新事物
NOT_SUPPORTED当前有事物,挂起;不使用事物
NEVER
NESTED

3)事物隔离级别 isolation

隔离级别中文说明
DEFAULT默认值默认数据库设定的隔离级别
READ_UNCOMMITTED读未提交最低的隔离级别,一个事务可以读取另一个事务未提交的数据。可能会导致脏读、幻读或不可重复读
READ_COMMITTED读已提交
REPEATABLE_READ可重复读
SERIALIZABLE可串行化

数据库默认的隔离级别是:
查看命令:SELECT @@SESSION.TRANSACTION_ISOLATION;

在这里插入图片描述

4)TransactionManager事物管理器

5)从Debug出发,大概查看整个执行流程

Controller调取Service生成代理类忽略,直接从实现层加入事物注解谈起。
在这里插入图片描述
在这里插入图片描述
我们可以看到,通过spring CGLIB生成了一个代理类 sig2.
在这里插入图片描述
在这里插入图片描述
直接新建一个CglibMethodInvocation.详情见细节。
在这里插入图片描述
直接走到父类的执行方法里。拦截器直接调用Invoke
在这里插入图片描述
在这里插入图片描述

我们会发现,拦截器里有多个实现类,我们的目标实现肯定也在其中。

在这里插入图片描述

public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {

最终走到了TransactionAspectSupport,核心在这。
在这里插入图片描述
创建一个新事物,只有事物传播方式满足条件的,才会走创建逻辑。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
点击obtainDataSource().getConnection(); 我们会发现有好多的连接方式,这点在哪里配置的呢?回头去看一眼配置文件配置的什么呢?
在这里插入图片描述

在这里插入图片描述
根据需要初始化事物同步。
在这里插入图片描述
在这里插入图片描述
至此一层流程到此结束。

四、try-catch会影响事物执行吗?

1)try catch只是对异常是否可以被@Transactional 感知 到有影响。如果错误抛到切面可以感知到的地步,那就可以起作用。

从demo入手,大家会发现,@Transactional(rollbackFor = Exception.class),直接配置了回滚异常类。Exception.class

@Transactional(rollbackFor = Exception.class)
	public void saveInspectInfoB() {
		try {
			//事物操作方法3
			taskInspectMapper.updateDataStatus(126389155885236224L);

			this.saveInspectInfoC();
			//回滚
			TaskInspect taskInspect = new TaskInspect();
			taskInspect.setProject("sss");
			//事物操作方法2
			taskInspectMapper.insertSelective(taskInspect);
		}catch (Exception e){
			throw new BizException(e);
		}
	}

	public void saveInspectInfoC(){
		//事物操作方法1
		taskInspectMapper.updateDataStatus(126387703699750912L);
	}

我们有rollbackFor,且用了try catch,试问会回滚吗?
在这里插入图片描述
在这里插入图片描述
答案是:会回滚
在这里插入图片描述
这种情况呢?会怎样?
在这里插入图片描述
在这里插入图片描述
catch住了,无报错信息。回滚失败,这是为何呢?通俗理解即可,本来我的rollbackFor时刻盯着是否报错呢,结果你中间catch插进来了,相当于最开始两个人传话,后来中间又加了一个人,那么如果中间这个人如实告知,当一个很好的传声筒,那就不会有问题,假如他接收了第一个人的信息,结果却没告诉第三个人,那么信息肯定就中断了,我并不知道到底发生了什么。

2)对于@Transactional可以保证RuntimeException错误的回滚,如果想保证非RuntimeException错误的回滚,需要加上rollbackFor = Exception.class 参数。

在这里插入图片描述
在这里插入图片描述
方法A单纯的加了事物注解,方法B也加了事物注解,且rollbackFor了。

在这里插入图片描述
结果是:回滚。

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值