一、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发生异常时,父事务回滚则子事务也跟着回滚了,所有数据都不会存入数据库