事务的传播行为嵌套事务的使用

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

事务的传播行为嵌套事务的使用
一、前言:
事务的传播行为(propagation)就是
为了解决外层方法调用内层事务方法的各个情
况的。

接下来要说的嵌套事务的使用是基于
Spring声明式事务管理中的注解
@Transactional方式的。

二、事务的传播行为:
1.@Transactional(propagation=Propagation.REQUIRED) :如果外层调用方法本身有事务, 那么就加入到该事务中, 没有的话新建一个(这是默认的设置项)
2.@Transactional(propagation=Propagation.NOT_SUPPORT ED) :以非事务方式运行,如果外层调用方法存在事务,则把当这个事务挂起。

3.@Transactional(propagation=Propagation.REQUIRES_ NEW) :不管外层调用方法否存在事务,都创建一个自己的事务,外层调用方法的事务挂起,自己的执行完毕,再执行调用方事务
4.@Transactional(propagation=Propagation.MANDATORY) :如果外层调用方法存在事务,则加入该事务;如果外层调用方法没有事务,则抛出异常
5.@Transactional(propagation=Propagation.NEVER) :以非事务方式运行,如果外层调用方法存在事务,则抛出异常。

6.@Transactional(propagation=Propagation.SUPPORTS) :如果外层调用方法存在事务,则加入该事务;如果外层调用方法没有事务,则以非事务的方式继续运行。

7.@Transactional(propagation=Propagation.NESTED) :如果外层调用方法存在事务,则创建一个事务作为当前事务的嵌套事务
来运行;如果外层调用方法没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED
三、关于事务传播行为:
传播行为就是一个约定:“别的方法调用
自己的时候会以怎样的方式开启事务”。

当你给一个方法指定传播行为的时候这时
这个方法本身肯定是支持事务的方法,然而调
用你的方法却不一定。

调用你的方法可能本身是个事务方法
(service事务方法a调用service事务方法b,
也可能不是(controller调用service事务方法
b / service非事务方法a调用service事务
方法b)
然后就看传播行为了。

Spring默认的是
PROPAGATION_REQUIRED
事务的传播行为我们一般都是用来解决嵌套事务的,所以我们一般使用最多的是上面加黑的三种:
四、嵌套事务:
嵌套事务:就是事务方法A调用事务方法
B,外层调用方法和内层被调用方法都是事务
方法的情况。

一般我们不关心外层调用方法的事务传播行为(用默认的(不指定就行))。

而只关心内层被调用方法的传播行为。

我们一般情况下,会有以下三种需求:
1.外层调用方法和内层被调用方法,有异常一起回滚,没问题一起提交。

(共用一个事务)
2.内层被调用方法回滚与否,不会影响外层调用方法。

而外层调
用方法出异常回滚,也不会回滚内层被调用方法(两个独立的事务)
3.内层被调用方法回滚与否,不会影响外层调用方法。

而外层调用方法出异常回滚,也会回滚内层被调用方法(嵌套事务)这三种情况正好对应三种最常用的传播行为
1----
->@Transactional(propagation=Propagation.REQUIRED) :内外层方法共用外层方法的事务
2----
->@Transactional(propagation=Propagation.REQUIRES_NE W) :
当执行内层被调用方法时,外层方法的事务会挂起。

两个事务相互独立,不会相互影响。

3----
->@Transactional(propagation=Propagation.NESTED) :理解Nested的关键是savepoint。

他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,
而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。

也就是说,如果父事务最后回滚,他也要回滚的。

它看起来像这样
class ServiceA {
public void methodA() {
// 数据库操作等其他代码
try {
// savepoint(虚拟的)
ServiceB.methodB(); // PROPAGATION_NESTED 级别
} catch (SomeException) {
// 执行其他业务, 如ServiceC.methodC();
}
// 其他操作代码
}
}
也就是说ServiceB.methodB失败回滚,那么ServiceA.methodA也会回滚到savepoint点上,ServiceA.methodA 可以选择另外一个分支,比如
ServiceC.methodC,继续执行,来尝试完成自己的事务。

五、嵌套事务的使用:
关于使用我的代码放到了我的github上了。

1、propagation=Propagation.REQUIRED的情况
内层被调用事务方法
@Transactional(propagation=Propagation.REQUIRED)
public void testRequired(User inner) {
testDAO.insertUser(inner);
}
外层调用方法
@Override
//@Transactional(propagation=Propagation.REQUIRED) // 调用方法可以是事务方法也可以是非事务方法
public void testRequired(User outer, User inner) {
testDAO.insertUser(outer);
try{
innerBean.testRequired(inner);
} catch(RuntimeException e){
log.error("内层方法出现异常回滚",e);
}
}
抛异常是通过,插入的User对象的UserName重复控制的,然后观察数据库就可以看到相应的情况结果。

(你可以把代码下载下来自己跑一下)
测试Main方法如下
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
OuterBean outerBean = (OuterBean) ctx.getBean("outerBeanImpl");
/**你能通过控制插入的数据的UserName重复产生异常*/
User outer = new User();
outer.setUsername("009");
outer.setName("zjl");
User inner = new User();
inner.setUsername("010");
inner.setName("zjl");
/** 选择代码进行注释,完成你想要的测试*/
outerBean.testRequired(outer, inner);
// outerBean.testRequiresNew(outer,inner);
//outerBean.testNested(outer,inner);
}
这种传播行为能实现:外层调用方法和内层被调用方法,有异常一起回滚,没问题一起提交
2、propagation=Propagation.REQUIRES_NEW
内层被调用事务方法
@Override
@Transactional(propagation=Propagation.REQUIRES_NEW) public void testRequiresNew(User inner) {
testDAO.insertUser(inner);
}
外层调用方法
@Override
@Transactional(propagation=Propagation.REQUIRED)
public void testRequiresNew(User outer, User inner) {
testDAO.insertUser(outer);
try{
innerBean.testRequiresNew(inner);
} catch(RuntimeException e){
log.error("内层方法出现异常回滚",e);
}
}
测试方法相同
这种传播行为能实现:内层被调用方法回滚与否,不会影响外层调用方法。

而外层调用方法出异常回滚,也不会回滚内层被调用方法
3、propagation=Propagation.NESTED
内层被调用事务方法
@Override
@Transactional(propagation=Propagation.NESTED)
public void testNested(User inner) {
testDAO.insertUser(inner);
}
外层调用方法
@Override
@Transactional(propagation=Propagation.REQUIRED)
public void testNested(User outer, User inner) {
testDAO.insertUser(outer);
try{
innerBean.testNested(inner);
} catch(RuntimeException e){
log.error("内层方法出现异常回滚",e);
}
}
测试方法相同
这种传播行为能实现:内层被调用方法回滚与否,不会影响外层调用方法。

而外层调用方法出异常回滚,也会回滚内层被调用方法
六、使用中的注意事项:
1、外层调用内层方法是两个事务的都要try catch 住调用内层方法的代码块。

共用一个事务的不要try catch 住(要不就出下面2那个异常)。

因为Spring声明式事务处理是基于Aop
的,默认情况下他会在方法抛出运行时异常时,
拦截异常回滚事务,然后会继续向上抛出。

所以你要try catch 住要不外层调用方法会用
相应异常,那传播行为就没有用了。

// 就类似这种
@Override
@Transactional(propagation=Propag
ation.REQUIRED)
public void testNested(User outer,
User inner) {
testDAO.insertUser(outer);
try{
innerBean.testNested(inner);
} catch(RuntimeException e){
log.error("内层方法出现异常回滚",e);
}
}
2、“Transaction rolled back because it has been marked as rollback-only”异常的出现
出现场景:这种异常一般是在,嵌套事务
使用中,内层事务使用默认的事务传播行为
(Propagation.REQUIRED),内外共用一
个事务时,外层方法把内层方法try catch 住
了,就会出现。

原因:内层方法出异常了,会向上抛异常,
SpringAOP拦截到,就会把事务标志为
rollback only,就是准备要回滚。

由于内外方法共用一个事务,这时要是外
层方法把这个异常捕获了,外层方法就继续提
交。

但是事务标记已经置了,那就会抛这个异
常。

3、同一的类的事务方法是无法直接调用的,如果ServiceA.methodA调用 Service.methodB,会使被调用方法的事务失效
因为spring的事务是基于代理类来实现
的。

在controller里的service其实是代理对
象,所以b方法的事务有效。

,而在同一个类
中ServiceA.methodA调用
Service.methodB,你拿到的不是代理后的
methodB,所以事务会失效
解决方法很简单,在methodA方法类中获取当前对象的代理对象
ServiceA proxy =(ServiceA)AopContext.currentProxy();
proxy.b();。

相关文档
最新文档