文章目录
一、什么是Spring事务的传播行为?
二、事务传播行为的七种类型
三、Propagation.REQUIRED(默认)
如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。
例子如下,后续各种情况将此例子展开讲解。
......
// ===========测试类
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans() {
saveParent();
saveChildren();
}
......
// ===========service实现类
public void saveParent() {
Stu stu = new Stu();
stu.setName("parent");
stu.setAge(19);
stuMapper.insert(stu); // 数据库插入一条parent记录
}
// @Transactional(propagation = Propagation.REQUIRED)
public void saveChildren() {
saveChild1();
int a = 1 / 0;
saveChild2();
}
public void saveChild1() {
Stu stu1 = new Stu();
stu1.setName("child-1");
stu1.setAge(11);
stuMapper.insert(stu1); // 数据库插入一条child-1记录
}
public void saveChild2() {
Stu stu2 = new Stu();
stu2.setName("child-2");
stu2.setAge(22);
stuMapper.insert(stu2); // 数据库插入一条child-2记录
}
结果是除以0错误java.lang.ArithmeticException: / by zero
这个例子中,有一个子方法saveParent()是没写事务注解的,用于对照比较有事务的子方法saveChildren()
情况一:
父方法testPropagationTrans()开启事务,子方法saveChildren()没有开启事务。
子方法saveChildren()和saveParent()同处在父方法的事务中,saveChildren()除以0异常导致事务中对数据库的操作都回滚。所以没有记录插入。

情况二:
父方法testPropagationTrans()不开启事务,只有saveChildren()开启事务
......
// ==========测试类
// @Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans() {
saveParent();
saveChildren();
}
......
// ===========service实现类
public void saveParent() {
Stu stu = new Stu();
stu.setName("parent");
stu.setAge(19);
stuMapper.insert(stu); // 数据库插入一条parent记录
}
@Transactional(propagation = Propagation.REQUIRED)
public void saveChildren() {
saveChild1();
int a = 1 / 0;
saveChild2();
}
public void saveChild1() {
Stu stu1 = new Stu();
stu1.setName("child-1");
stu1.setAge(11);
stuMapper.insert(stu1); // 数据库插入一条child-1记录
}
public void saveChild2() {
Stu stu2 = new Stu();
stu2.setName("child-2");
stu2.setAge(22);
stuMapper.insert(stu2); // 数据库插入一条child-2记录
}

只有saveChildren()开启事务,saveChildren()发生异常就回滚该方法里面的数据库操作,所以数据库插入一条parent记录还是可以成功的。
情况三:
父方法testPropagationTrans()和子方法saveChildren()均开启事务。这里不拉出多余代码,文字解释+结果图即可。

saveChildren()发生异常,开始回滚,异常继续往上抛,父方法testPropagationTrans()也知道发生了异常,父方法里面所有数据库操作都回滚,所以saveParent()数据库插入的parent记录也回滚,最后数据库没有数据插入。
疑问:异常继续往上抛父方法才知道发生了异常,导致方法里所有数据库操作回滚,那么我把这个异常try-catch,是不是就只有saveChildren()回滚呢?那可不一定,跟着我继续来看。
四、将异常try-catch捕获,事务是否还会回滚?(这个总结很重要)
所有的讲解都是围绕开头第一段代码,依然是情况一二三,只不过此时多了
try-catch
......
public void testPropagationTrans() {
saveParent();
try {
saveChildren();
} catch (Exception e) {
e.printStackTrace();
}
}
......
情况一:
父方法testPropagationTrans()开启事务,子方法saveChildren()没有开启事务。

saveChildren()产生的异常被捕获,没有继续上抛,父方法开启的事务不会回滚,故插入2条数据。
情况二:
父方法testPropagationTrans()不开启事务,只有saveChildren()开启事务.

saveChildren()发生异常,回滚数据,parent记录插入不受影响。
情况三:
父方法testPropagationTrans()和子方法saveChildren()均开启事务。

结果发现即使saveChildren()产生的异常被try-catch,父事务也回滚。
五、Propagation.SUPPORTS
如果当前有事务,则使用事务,如果当前没有事务,就以非事务方式执行
情况一:
父方法testPropagationTrans()不开启事务,子方法saveChildren()事务传播类型改为Propagation.SUPPORTS

外层父方法没有事务,子方法saveChildren()也就以非事务方式执行,这里不会回滚,所以有2条数据。
情况二:
父方法testPropagationTrans()开启事务,传播类型为Propagation.REQUIRED,子方法saveChildren()事务传播类型改为Propagation.SUPPORTS

子方法saveChildren()当前父方法开启了事务,故使用事务,saveChildren()发生异常回滚,这里子事务没将父事务挂起,子事务回滚,父事务一定回滚,正好验证了前面说过的结论,所以这里没有记录。
六、Propagation.MANDATORY
情况二:
父方法testPropagationTrans()开启事务,传播类型为Propagation.REQUIRED,子方法saveChildren()事务传播类型改为Propagation.MANDATORY

子方法saveChildren()支持父事务,故使用事务,saveChildren()发生异常回滚,这里子事务没将父事务挂起,子事务回滚,父事务一定回滚,正好验证了前面说过的结论,所以这里没有记录。
七、Propagation.REQUIRES_NEW
新建事务,如果当前存在事务,把当前事务挂起。
这里说得多一些,所以代码整个给出来
......
// ==========测试类
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans() {
saveParent();
saveChildren();
}
......
// ===========service实现类
public void saveParent() {
Stu stu = new Stu();
stu.setName("parent");
stu.setAge(19);
stuMapper.insert(stu); // 数据库插入一条parent记录
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveChildren() {
saveChild1();
int a = 1 / 0;
saveChild2();
}
public void saveChild1() {
Stu stu1 = new Stu();
stu1.setName("child-1");
stu1.setAge(11);
stuMapper.insert(stu1); // 数据库插入一条child-1记录
}
public void saveChild2() {
Stu stu2 = new Stu();
stu2.setName("child-2");
stu2.setAge(22);
stuMapper.insert(stu2); // 数据库插入一条child-2记录
}

有人会疑问了,saveChildren() 都新建事务将原事务挂起了,为什么子事务回滚父事务也会回滚?原来的事务都挂起了,子事务回滚和父事务回滚没有必然联系了。 其实这里原因是因为异常抛给了父事务,导致回滚。我们可以将saveChildren()用try-catch包裹,就会发现,testPropagationTrans()所在的事务并没有回滚,因为parent记录插入成功了。

为了不混淆,我们将异常提出来
......
// ==========测试类
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans() {
saveParent();
saveChildren();
int a = 1 / 0; // =======将saveChildren的异常提到外面=============
}
......
// ===========service实现类
public void saveParent() {
Stu stu = new Stu();
stu.setName("parent");
stu.setAge(19);
stuMapper.insert(stu); // 数据库插入一条parent记录
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveChildren() {
saveChild1();
saveChild2();
}
public void saveChild1() {
Stu stu1 = new Stu();
stu1.setName("child-1");
stu1.setAge(11);
stuMapper.insert(stu1); // 数据库插入一条child-1记录
}
public void saveChild2() {
Stu stu2 = new Stu();
stu2.setName("child-2");
stu2.setAge(22);
stuMapper.insert(stu2); // 数据库插入一条child-2记录
}

两条child记录插入成功,而parent记录没插入,说明子事务执行完就commit了,父事务所有相关数据库的操作全部回滚,parent记录的插入被撤销,但这也影响不了已经commit的子事务。
举个形象的例子,小区的人都用小区网,我觉得小区网太慢了,自己拉了一根光纤,某天因施工小区网断掉了,大家都受到影响,但是我自己的网不受影响。
八、Propagation.NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
......
// ==========测试类
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans() {
saveParent();
saveChildren();
}
......
// ===========service实现类
public void saveParent() {
Stu stu = new Stu();
stu.setName("parent");
stu.setAge(19);
stuMapper.insert(stu); // 数据库插入一条parent记录
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void saveChildren() {
saveChild1();
int a = 1 / 0; // ===============这里有异常===========
saveChild2();
}
public void saveChild1() {
Stu stu1 = new Stu();
stu1.setName("child-1");
stu1.setAge(11);
stuMapper.insert(stu1); // 数据库插入一条child-1记录
}
public void saveChild2() {
Stu stu2 = new Stu();
stu2.setName("child-2");
stu2.setAge(22);
stuMapper.insert(stu2); // 数据库插入一条child-2记录
}

结果是child-1记录插入成功,parent没有插入成功。原因是saveChildren()以非事务方式执行,并将父事务挂起,执行之后发生异常,但是child-1插入成功,异常抛到父事务后数据库操作全部回滚,所以parent没有插入成功。
这种情况主要用在查询操作,比如在类上开启了事务,类里面的所有方法都开启了事务,插入删除更新是需要的,但是查询就没必要了,所以可以用这个Propagation.NOT_SUPPORTED将查询方法以非事务的方式执行。
九、Propagation.NEVER
以非事务方式执行,如果当前存在事务,则抛出异常。与Propagation.MANDATORY正好相反。
......
// ==========测试类
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans() {
saveParent();
saveChildren();
}
......
// ===========service实现类
public void saveParent() {
Stu stu = new Stu();
stu.setName("parent");
stu.setAge(19);
stuMapper.insert(stu); // 数据库插入一条parent记录
}
@Transactional(propagation = Propagation.NEVER)
public void saveChildren() {
saveChild1();
int a = 1 / 0; // ===============这里有异常===========
saveChild2();
}
public void saveChild1() {
Stu stu1 = new Stu();
stu1.setName("child-1");
stu1.setAge(11);
stuMapper.insert(stu1); // 数据库插入一条child-1记录
}
public void saveChild2() {
Stu stu2 = new Stu();
stu2.setName("child-2");
stu2.setAge(22);
stuMapper.insert(stu2); // 数据库插入一条child-2记录
}
结果异常Existing transaction found for transaction marked with propagation 'never'


数据库也没有记录,因为parent记录插入后,收到saveChildren()的异常导致父事务回滚,而saveChildren()因为注解检查到异常,内容就没执行。如果去掉testPropagationTrans()事务,那么执行如下,方法都是以非事务方式执行。

十、Propagation.NESTED
如果当前有事务,则开启子事务(嵌套事务),嵌套事务是独立提交或者回滚,如果当前没有事务,就新建事务运行。
运行结果和原因与Propagation.REQUIRED一模一样。几乎没区别,这种情况用得少。
欢迎一键三连~
有问题请留言,大家一起探讨学习
———————-Talk is cheap, show me the code———————–
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/202405.html原文链接:https://javaforall.net
