最近复习Spring,看到传播行为这里,有些许疑惑,然后各种百度,返现竟然没有详细的介绍,就几篇比较有质量的文章,没点耐心和基础还看不懂。经过一天多的反复钻研,我把我对传播行为的理解梳理一下。
我们什么时候会用到Spring的传播行为?
在我们使用Spring事务的时候,很有可能遇到一下这种情况在方法A调用方法B,方法B被注解称一个事务,然后这时候方法A要在方法B的基础上做一些操作,这个时候你就要想啊,我要不要把方法A也设置成一个事务呢?方法A种的一下操作想要和方法B保持一致性,有的方法又不想,这时候咋办呢?是不是蒙了?
这个时候就需要使用Spring的事务的传播行为了。
什么是事务的传播行为
什么是事务的传播行为呢,我的理解是当方法A上开启事务了,方法A中调用B这个时候,方法A中的事务能不能到达方法B这一层。
public void testPropagation(){
methodA(){
MethodDoSomethingBefor();
methodB();
MethodDoSomethingAfter();
}
}
@Transactional //开启事务
public void methodB(){
doSomething();
}
首先,事务由七种传播行为,我来一一解释下。
引用人家的图,特此鸣谢
首先要明确一点啊,这其中传播行为倒是配在被调用的方法上的,也就是方法B上,就是说当方法A种需要使用到方法B时,讨论这时候方法A事务是个什么鬼样子(重点)。不要像我一样一开始觉得会有7*7=49种排列组合,只有七种哦(方法A的注解是@Transactional(),方法B是这七种),这是往后看的前提。
下面结合代码,一个一个的分析:
PROPAGATION_REQUIRED: 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务。
public void methodA(){
customerMapper.insert(new Customer(5l,"我是第一个","你好",0));
methodB();
}
@Transactional //开启事务
public void methodB(){
orderMapper.insert(new Order(null ,10l,10l,10l));
int a =1/0;
}
现在是方法A没有开启事务,方法B开启了事务,两者表都是空的,执行方法A
结果:
结果是都插进去了,所以方法A如果没有开启事务的话,方法B也没法开启。
都开启以后就可以了,看结果rollBack了。
public void methodA() {
customerMapper.insert(new Customer(5l, "我是第一个", "你好", 0));
methodB();
int a = 1 / 0;
}
@Transactional(propagation = Propagation.REQUIRED) //开启事务
public void methodB() {
orderMapper.insert(new Order(null, 10l, 10l, 10l));
// int a =1/0;
}
这个是methodA()中有异常,结果(每次重新执行钱都把数据库清空了,也是回滚了
结论:方法B设置传播行为是PROPAGATION_REQUIRED以后,方法A和方法B整合为一个事务,并且方法A必须开启事务,要不然不会回滚,为什么不会回滚呢,前面提到过,是方法A中的事务能不能到达方法B这一层,如果A不开启事务,则默认是没有开启事务,方法B也得不到传递。
PROPAGATION_SUPPORTS: 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行
我的理解就是随着方法A,方法A有事务就按事务执行,方法A没有,就不按事务执行。
方法A开启事务,抛异常,没有执行,回滚了
@Transactional()
public void methodA() {
customerMapper.insert(new Customer(5l, "我是第一个", "你好", 0));
methodB();
// int a = 1 / 0;
System.out.println("执行了");
}
@Transactional(propagation = Propagation.SUPPORTS) //开启事务
public void methodB() {
orderMapper.insert(new Order(null, 10l, 10l, 10l));
int a =1/0;
}
方法A关闭事务,插入进去了
// @Transactional()
public void methodA() {
customerMapper.insert(new Customer(5l, "我是第一个", "你好", 0));
methodB();
// int a = 1 / 0;
System.out.println("执行了");
}
@Transactional(propagation = Propagation.SUPPORTS) //开启事务
public void methodB() {
orderMapper.insert(new Order(null, 10l, 10l, 10l));
int a =1/0;
}
结论,跟随方法A,开启事务,抛异常时数据回滚,不开启事务,可以插入。也符合第一条说的,事务的传播行为时看事务能不能传递到B这一程。
PROPAGATION_MANDATORY: 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
public void methodA() {
customerMapper.insert(new Customer(5l, "我是第一个", "你好", 0));
bookService.methodB();
// int a = 1 / 0;
System.out.println("执行了");
}
这个方法在bookService中,如果是同一个方法,不会抛异常(所以这也要注意,当使用事务的传播行为的时候,要是两个service)
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
orderMapper.insert(new Order(null, 10l, 10l, 10l));
// int a =1/0;
}
重点
PROPAGATION_REQUIRED_NEW 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager
这个表示,在方法A的基础上再建一个事务,相互独立。看代码
@Transactional
public void methodA() {
try{
customerMapper.insert(new Customer(5l, "我是第一个", "你好", 0));
bookService.methodB();
}catch (Exception e){
customerMapper.insert(new Customer(5l, "我是第二个", "你好", 0));
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
orderMapper.insert(new Order(null, 10l, 10l, 10l));
int a =1/0;
}
结果一个插入两条,一个没有插入,是不是很疑惑?我们一点点梳理。
首先介绍一个概念,事务挂起,就是代码事务进去等待状态,等其他的代码执行完,再执行。
再说,PROPAGATION_REQUIRED_NEW,它是不管有没有事务,都重新开启一个事务,或者这样表示
main(){
TransactionManager tm = null;
try{
//获得一个JTA事务管理器
tm = getTransactionManager();
tm.begin();//开启一个新的事务
Transaction ts1 = tm.getTransaction();
doSomeThing();
tm.suspend();//挂起当前事务
try{
tm.begin();//重新开启第二个事务
Transaction ts2 = tm.getTransaction();
methodB();
ts2.commit();//提交第二个事务
}
Catch(RunTimeException ex){
ts2.rollback();//回滚第二个事务
}
finally{
//释放资源
}
//methodB执行完后,复恢第一个事务
tm.resume(ts1);
doSomeThingB();
ts1.commit();//提交第一个事务
}
catch(RunTimeException ex){
ts1.rollback();//回滚第一个事务
}
finally{
//释放资源
}
}
这里引用了别人博客的代码,特此鸣谢
我们都知道,事务的原理就是一个 try{}catch{}finally{}的形式,有异常回滚,没有问题commit,最后释放资源,这里的PROPAGATION_REQUIRED_NEW就是在方法A的try{}catch{}中又做了一层try{}catch{},这就有可能诞生很多的情况
1).方法B中有异常,方法B中的操作回滚,不会影响到方法A的操作(前提是方法try{}catch{}了)
2).方法A中有异常,但是方法B中的操作可以正常进行,因为方法B在自己的事务中commit了已经
@Transactional
public void methodA() {
customerMapper.insert(new Customer(5l, "我是第一个", "你好", 0));
bookService.methodB();
int a=1/0;
customerMapper.insert(new Customer(5l, "我是第二个", "你好", 0));
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
orderMapper.insert(new Order(null, 10l, 10l, 10l));
}
总结 :PROPAGATION_REQUIRED_NEW 不管有没有都重新开启一个事务
PROPAGATION_NOT_SUPPORTED: 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager(这里又提到了挂起,也就是说,这里不管你有没有事务,我都执行,并且不影响A的执行)
@Transactional
public void methodA() {
try {
customerMapper.insert(new Customer(5l, "我是第一个", "你好", 0));
bookService.methodB();
// int a=1/0;
}catch (Exception e){
customerMapper.insert(new Customer(5l, "我是第二个", "你好", 0));
}
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void methodB() {
orderMapper.insert(new Order(null, 10l, 10l, 10l));
int a =1/0;
}
总结: PROPAGATION_NOT_SUPPORTED 不支持事务,有事务就要被挂起**
PROPAGATION_NEVER: 这个和PROPAGATION_MANDATORY正好相反,这个是有事务抛异常
@Transactional
public void methodA() {
customerMapper.insert(new Customer(5l, "我是第一个", "你好", 0));
bookService.methodB();
}
@Transactional(propagation = Propagation.NEVER)
public void methodB() {
orderMapper.insert(new Order(null, 10l, 10l, 10l));
}
PROPAGATION_NESTED: 如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。需要JDBC 驱动的java.sql.Savepoint类。有一些JTA的事务管理器实现可能也提供了同样的功能。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true;而 nestedTransactionAllowed属性值默认为false;
是不是很懵,直接上代码逻辑
main(){
Connection con = null;
Savepoint savepoint = null;
try{
con = getConnection();
con.setAutoCommit(false);
doSomeThingA();
savepoint = con.setSavepoint();
try{
methodB();
}catch(RuntimeException ex){
con.rollback(savepoint);
}
finally{
//释放资源
}
doSomeThingB();
con.commit();
}
catch(RuntimeException ex){
con.rollback();
}
finally{
//释放资源
}
}
是不是看着跟REQUIRED_NEW没有区别,不都是在新建一个事务吗,区别就在于上面说的,他是基于JDBC 3.0驱动的,savepoint = con.setSavepoint();,并且在内层少了一个commit这代表什么呢,就是内存出现问题,只会滚内层到savepoint ,外层出现问题全部回滚。
// @Transactional
public void methodA() {
try{
customerMapper.insert(new Customer(5l, "我是第一个", "你好", 0));
bookService.methodB();
// int a=1/0;
}catch (Exception e){
}
}
@Transactional(propagation = Propagation.NESTED)
public void methodB() {
orderMapper.insert(new Order(null, 10l, 10l, 10l));
int a =1/0;
}
这里外层没有开启事务,只有内层回滚了,外层执行成功
@Transactional(propagation = Propagation.NESTED)
public void methodB() {
orderMapper.insert(new Order(null, 10l, 10l, 10l));
// int a =1/0;
}
@Transactional
public void methodA() {
try{
customerMapper.insert(new Customer(5l, "我是第一个", "你好", 0));
bookService.methodB();
int a=1/0;
}catch (Exception e){
}
}
最后我发现一个问题
@Transactional
public void methodA() {
try{
customerMapper.insert(new Customer(5l, "我是第一个", "你好", 0));
bookService.methodB();
int a=1/0;
}catch (Exception e){
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
orderMapper.insert(new Order(null, 10l, 10l, 10l));
// int a =1/0;
}
当外层有异常的时候,只要try{}catch{}也执行成功了,说明了spring的事务其实就是基于AOP在我们主要代码逻辑上加了获取连接,关闭自动提交,开启事务,catch异常打印异常信息,回滚,最后关闭资源的工作,如果自己try{}catch{}了 那么就不会回滚了
参考:https://www.cnblogs.com/yixianyixian/p/8372832.html
https://www.open-open.com/lib/view/open1350865116821.html