一篇文章带你读懂Spring 事务——事务的传播机制

一、spring事务的传播

一句话概括就是多个事务方法在相互调用时,事务如何在这些方法之间传播。

可能说个举例更容易懂
在这里插入图片描述

现在有两个方法,方法A(有事务)和方法B,方法A调用方法B,方法B有无事务以及对事务要求不同都会对方法A的事务执行造成影响,这种影响就是由两个方法所定义的事务传播类型所决定

在这里插入图片描述

二、事务传播类型枚举Propagation介绍

在Spring中对于事务的传播行为定义了七种类型

	REQUIRED(0),
    SUPPORTS(1),
    MANDATORY(2),
    REQUIRES_NEW(3),
    NOT_SUPPORTED(4),
    NEVER(5),
    NESTED(6);

在TransactionDefinition接口中定义了Spring事务的一些属性,不仅包括事务传播特性类型,还包括了事务的隔离级别类型

package java.lang.annotation;

/**
 * Annotation retention policy.  The constants of this enumerated class
 * describe the various policies for retaining annotations.  They are used
 * in conjunction with the {@link Retention} meta-annotation interface to
 * specify how long annotations are to be retained.
 *
 * @author  Joshua Bloch
 * @since 1.5
 */
public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

三、详解与示例

我们先设计一个场景,帮助大家理解,场景描述如下

方法A执行会在数据库tableA插入一条数据,方法B执行会在数据库tableB插入一条数据,代码如下:

//将传入参数a存入tableA
pubilc void A(a){
    tableBtableA(a);    
}
//将传入参数b存入tableB
public void B(b){
    tableBtableB(b);
}

没有事务

public void main(){
    A(a1);  
    testB();    
}

public void testB(){
    B(b1);  
    throw Exception;     //模拟异常
    B(b2);  
}

切记使用this.方法名来调用,this.方法名调用是对象内部方法调用,不会通过Spring代理,会导致事务失效

结果:
a1数据成功存入tableA表
b1数据成功存入tableB表
由于b2数据之前抛了异常,所以b2数据没有插入数据库

REQUIRED(默认传播类型)

如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务

  • 场景1(当前存在事务,加入):main和testB上声明事务,设置传播行为REQUIRED,代码如下:

    	@Transactional(propagation = Propagation.REQUIRED)
    	public void main(){
    	    A(a1);  
    	    testB();    
    	}
    	@Transactional(propagation = Propagation.REQUIRED)
    	public void testB(){
    	    B(b1);  
    	    throw Exception;     //模拟异常
    	    B(b2);  
    	}
    

分析:由于主方法和testB方法的事务传播类型都是required,在执行testB方法时,会加入到main的事务,在方法testB中有异常,main和testB又在同一个事务中,所以两个方法中的事务都会回滚,数据库中没添加一条数据

  • 场景2(当前方法没有事务,新建):只在testB上声明事务,设置传播行为REQUIRED,代码如下:
public void main(){
    A(a1);  
    testB();    
}
@Transactional(propagation = Propagation.REQUIRED)
public void testB(){
    B(b1);  
    throw Exception;     //模拟异常
    B(b2);  
}

分析:main方法中没有事务,在执行方法b的时候,由于方法b事务的传播机制是required,所以方法b自己会新建一个事务,方法b中有异常,方法b的操作回滚,但由于main和方法b不在同一个事务中(方法b的事务是自己新建的),所以,方法a的数据存储到数据库中,方法b的数据没有存储到数据库

SUPPORTS

当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行

  • 场景1(没有事务,以非事务方法执行):只在testB上声明事务,设置传播行为SUPPORTS,代码如下:

    public void main(){
        A(a1);  
        testB();    
    }
    @Transactional(propagation = Propagation.SUPPORTS)
    public void testB(){
        B(b1);  
        throw Exception;     //模拟异常
        B(b2);  
    }
    

分析:main方法没有事务,在调用方法b的时候,由于方法b的传播机制是supports,所以方法b也是没有事务的,两个方法都已非事务进行,在遇到异常不会回滚,最终a1、b1存储到数据库,b2没有存储到数据库

  • 场景2(当前存在事务,则加入当前事务):在main方法上声明事务,设置传播行为是required ,在testB上声明事务,设置传播行为SUPPORTS,代码如下:

    @Transactional(propagation = Propagation.REQUIRED)
    public void main(){
        A(a1);  
        testB();    
    }
    @Transactional(propagation = Propagation.SUPPORTS)
    public void testB(){
        B(b1);  
        throw Exception;     //模拟异常
        B(b2);  
    }
    

分析:现在main方法有了事务,在调用方法b的时候,会加入到main方法的事务中去,a1,b1和b2都不会存储到数据库

MANDATORY

当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。

  • 场景1(当前事务不存在):只在testB上声明事务,设置传播行为MANDATORY,代码如下:
public void main(){
    A(a1);  
    testB();    
}
@Transactional(propagation = Propagation.MANDATORY)
public void testB(){
    B(b1);  
    throw Exception;     //模拟异常
    B(b2);  
}

分析:main方法没有声明事务,在执行testB方法时就直接抛出事务要求的异常,所以,a1导入数据库,b1和b2不会存储到数据库

  • 场景2(当前事务存在):main方法上声明事务,然后在testB上声明事务,设置传播行为MANDATORY,代码如下:
@Transactional(propagation = Propagation.REQUIRED)
public void main(){
    A(a1);  
    testB();    
}
@Transactional(propagation = Propagation.MANDATORY)
public void testB(){
    B(b1);  
    throw Exception;     //模拟异常
    B(b2);  
}

分析:在main方法进行事务声明,并且设置为REQUIRED,则执行testB时就会使用main已经开启的事务,遇到异常就正常的回滚了。所以,a1,b1和b2不会存储到数据库

REQUIRES_NEW

创建一个新事务,如果存在当前事务,则挂起该事务。

换句话说,在执行时,不论当前是否存在事务,总是会新建一个事务。

  • 场景1:这次的异常在main方法,然后给main声明事务,传播类型设置为REQUIRED,testB也声明事务,设置传播类型为REQUIRES_NEW,代码如下
@Transactional(propagation = Propagation.REQUIRED)
public void main(){
    A(a1);  
    testB();    
    throw Exception;     //模拟异常
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void testB(){
    B(b1);  
    B(b2);  
}

分析:main方法调用方法b时,由于船舶类型是REQUIRES_NEW,所以方法b会新建一个事务(注意这个事务和main方法的事务是两个不同的事务),所以,最终结果就是a1没有存储成功,b1和b2存储成功了

NOT_SUPPORTED

始终以非事务方式执行,如果当前存在事务,则挂起当前事务

始终以非事务的方式运行。

  • 场景:main传播类型设置为REQUIRED,testB传播类型为NOT_SUPPORTED,代码如下
@Transactional(propagation = Propagation.REQUIRED)
public void main(){
    A(a1);  
    testB();    
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void testB(){
    B(b1);  
    throw Exception;     //模拟异常
    B(b2);  
}

分析:main方法有事务,执行到方法b的时候,以非事务进行,b1保存成功,方法b抛异常,main方法检测到异常,事务回滚,由于方法b不在事务中,所以,a1和b2保存失败,b1保存成功

NEVER

不使用事务,如果当前事务存在,则抛出异常

  • 场景:main设置传播类型为REQUIRED,testB传播类型设置为NEVER,代码如下
    注意:这次代码中没有异常
@Transactional(propagation = Propagation.REQUIRED)
public void main(){
    A(a1);  
    testB();    
}
@Transactional(propagation = Propagation.NEVER)
public void testB(){
    B(b1);  
    B(b2);  
}

分析:因为main方法在调用方法b的时候,方法b传播类型为never,main方法有事务,所以直接抛异常,回滚数据,最终,a1 , b1, b2都不会进数据库

NESTED

如果当前事务存在,则在嵌套事务中执行,否则开启一个事务
嵌套事务也称为父子事务

  • 场景,main设置为REQUIRED,testB设置为NESTED,且异常发生在main中,代码如下
@Transactional(propagation = Propagation.REQUIRED)
public void main(){
    A(a1);  
    testB();    
    throw Exception;     //模拟异常
}
@Transactional(propagation = Propagation.NESTED)
public void testB(){
    B(b1);  
    B(b2);  
}

分析:main发生异常时,父事务回滚则子事务也跟着回滚了,所有数据都不会存入数据库

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值