Spring propagation7种事务配置
1、简述
在声明式的事务处理中,要配置一个切面,其中就用到了propagation,表示打算对这些方法怎么使用事务,是用还是不用,其中propagation有七种配置,REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED。默认是REQUIRED。
2、Spring中七种Propagation类的事务属性详解:
REQUIRED
:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
SUPPORTS
:支持当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORY
:支持当前事务,如果当前没有事务,就抛出异常。
REQUIRES
_NEW:新建事务,如果当前存在事务,把当前事务挂起。
NOT_SUPPORTED
:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NEVER
:以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED
:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。
3、注意事项
这些配置将影响数据存储,必须根据情况选择。
@Transactional事务几点注意及其属性Propagation的使用
@Transactional事务几点注意
这里面有几点需要大家留意:
A. 一个功能是否要事务,必须纳入设计、编码考虑。不能仅仅完成了基本功能就ok。
B. 如果加了事务,必须做好开发环境测试(测试环境也尽量触发异常、测试回滚),确保事务生效。
C. 以下列了事务使用过程的注意事项,请大家留意。
1. 不要在接口上声明@Transactional ,而要在具体类的方法上使用 @Transactional 注解,否则注解可能无效。
2.不要图省事,将@Transactional放置在类级的声明中,放在类声明,会使得所有方法都有事务。故@Transactional应该放在方法级别,不需要使用事务的方法,就不要放置事务,比如查询方法。否则对性能是有影响的。
3.使用了@Transactional的方法,对同一个类里面的方法调用, @Transactional无效。比如有一个类Test,它的一个方法A,A再调用Test本类的方法B(不管B是否public还是private),但A没有声明注解事务,而B有。则外部调用A之后,B的事务是不会起作用的。(经常在这里出错)
4.使用了@Transactional的方法,只能是public,@Transactional注解的方法都是被外部其他类调用才有效,故只能是public。道理和上面的有关联。故在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错,但事务无效。
5.经过在ICORE-CLAIM中测试,效果如下:
A.抛出受查异常XXXException,事务会回滚。
B.抛出运行时异常NullPointerException,事务会回滚。
C.Quartz中,execute直接调用加了@Transactional方法,可以回滚;间接调用,不会回滚。(即上文3点提到的)
D.异步任务中,execute直接调用加了@Transactional方法,可以回滚;间接调用,不会回滚。(即上文3点提到的)
E.在action中加上@Transactional,不会回滚。切记不要在action中加上事务。
F.在service中加上@Transactional,如果是action直接调该方法,会回滚,如果是间接调,不会回滚。(即上文3提到的)
G.在service中的private加上@Transactional,事务不会回滚。
其属性Propagation的使用:
Spring Transaction中有一个很重要的属性:Propagation。主要用来配置当前需要执行的方法,与当前是否有transaction之间的关系。
我晓得有点儿抽象,这也是为什么我想要写这篇博客的原因。看了后面的例子,大家应该就明白了。
一、Propagation取值:
REQUIRED
(默认值):在有transaction状态下执行;如当前没有transaction,则创建新的transaction;
SUPPORTS
:如当前有transaction,则在transaction状态下执行;如果当前没有transaction,在无transaction状态下执行;
MANDATORY
:必须在有transaction状态下执行,如果当前没有transaction,则抛出异常IllegalTransactionStateException;
REQUIRES_NEW
:创建新的transaction并执行;如果当前已有transaction,则将当前transaction挂起;
NOT_SUPPORTED
:在无transaction状态下执行;如果当前已有transaction,则将当前transaction挂起;
NEVER
:在无transaction状态下执行;如果当前已有transaction,则抛出异常IllegalTransactionStateException。
二、REQUIRED与REQUIRED_NEW
上面描述的6种propagation属性配置中,最难以理解,并且容易在transaction设计时出现问题的是REQUIRED和REQURED_NEW这两者的区别。当程序在某些情况下抛出异常时,如果对于这两者不够了解,就可能很难发现而且解决问题。
下面我们给出三个场景进行分析:
场景一:
ServiceA.java:
public class ServiceA {
@Transactional
public void callB() {
serviceB.doSomething();
}
}
ServiceB.java
public class ServiceB {
@Transactional
public void doSomething() {
throw new RuntimeException("B throw exception");
}
}
这种情况下,我们只需要在调用ServiceA.callB时捕获ServiceB中抛出的运行时异常,则transaction就会正常的rollback。
场景二
在保持场景一中ServiceB不变,在ServiceA中调用ServiceB的doSomething时去捕获这个异常,如下:
public class ServiceA {
@Transactional
public void callB() {
try {
serviceB.doSomething();
} catch (RuntimeException e) {
System.err.println(e.getMessage());
}
}
}
这个时候,我们再调用ServiceA的callB。程序会抛出org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only这样一个异常信息。原因是什么呢?
因为在ServiceA和ServiceB中的@Transactional propagation都采用的默认值:REQUREID。
根据我们前面讲过的REQUIRED特性,当ServiceA调用ServiceB的时候,他们是处于同一个transaction中。
如下图所示:
当ServiceB中抛出了一个异常以后,ServiceB会把当前的transaction标记为需要rollback。
但是ServiceA中捕获了这个异常,并进行了处理,认为当前transaction应该正常commit。
此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException。
场景三
在保持场景二中ServiceA不变,修改ServiceB中方法的propagation配置为REQUIRES_NEW,如下:
public class ServiceB {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomething() {
throw new RuntimeException("B throw exception");
}
}
此时,程序可以正常的退出了,也没有抛出UnexpectedRollbackException。原因是因为当ServiceA调用ServiceB时,serviceB的doSomething是在一个新的transaction中执行的。
如下图所示:
所以,当doSomething抛出异常以后,仅仅是把新创建的transaction rollback了,而不会影响到ServiceA的transaction。
ServiceA就可以正常的进行commit。
当然这里把ServiceA和ServiceB放在两个独立的transaction是否成立,还需要再多多考虑你的业务需求。
Transaction不是一个新东西了,那对于transaction的使用会不会有一些模式?一些经验之谈呢?答案肯定是有的,以后再说。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。