一、什么是Spring事务的传播行为?
事务传播行为是指多个拥有事务的方法在嵌套调用时的事务控制方式
比如XML中配置:XML:<tx:method name="..." propagation="REQUIRED"/>
注解配置:@Transactional(propagation=Propagation.REQUIRED)
二、事务传播行为的七种类型
三、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
,父事务也回滚。
综上:
1. 父方法和子方法都开启事务,异常发生让子事务回滚,父事务一定回滚(子事务没将父事务挂起的情况下),不管是否被try-catch
包裹,第四节的情况三就是最好的例子。
2. 只要try-catch
在内层,@Transactional
在外层,异常被try-catch
住,事务就不会回滚。
3. 但是如果@Transactional
在内层,try-catch
在外层,那try-catch
还没来得及处理异常就在@Transactional
注解作用下回滚了,第四节的情况二就是最好的例子。
五、Propagation.SUPPORTS
如果当前有事务,则使用事务,如果当前没有事务,就以非事务方式执行
情况一:
父方法testPropagationTrans()
不开启事务,子方法saveChildren()
事务传播类型改为Propagation.SUPPORTS
外层父方法没有事务,子方法saveChildren()
也就以非事务方式执行,这里不会回滚,所以有2条数据。
情况二:
父方法testPropagationTrans()
开启事务,传播类型为Propagation.REQUIRED
,子方法saveChildren()
事务传播类型改为Propagation.SUPPORTS
子方法saveChildren()
当前父方法开启了事务,故使用事务,saveChildren()
发生异常回滚,这里子事务没将父事务挂起,子事务回滚,父事务一定回滚,正好验证了前面说过的结论,所以这里没有记录。
六、Propagation.MANDATORY
支持当前的事务,如果当前没有事务,就抛出异常。
情况一:
父方法testPropagationTrans()
不开启事务,子方法saveChildren()
事务传播类型改为Propagation.MANDATORY
此时saveChildren()
直接抛出异常,和之前的除以0异常不同, No existing transaction found for transaction marked with propagation 'mandatory'
,外层没有事务,就会抛异常。
异常导致saveChildren()
方法没执行,没有child-1
记录插入,但是数据库会插入parent
记录,因为父方法没开启事务,不影响saveParent()
的执行。
情况二:
父方法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-----------------------