上篇讲述了 当@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了。
结果是:回滚。