面试必问的事务-2.2:事务嵌套

通过分析一些事务嵌套的场景,来深入理解spring的事务传播机制。

1:简单举例分析

假设外层事务 Service A 的 Method A() 调用 内层Service B 的 Method B()

PROPAGATION_REQUIRED(spring 默认) required  (需要

如果ServiceB.methodB() 的事务级别定义为 PROPAGATION_REQUIRED,那么执行 ServiceA.methodA() 的时候spring已经起了事务,这时调用 ServiceB.methodB(),ServiceB.methodB() 看到自己已经运行在 ServiceA.methodA() 的事务内部,就不再起新的事务。

假如 ServiceB.methodB() 运行的时候发现自己没有在事务中,他就会为自己分配一个事务。

这样,在 ServiceA.methodA() 或者在 ServiceB.methodB() 内的任何地方出现异常,事务都会被回滚。

PROPAGATION_REQUIRES_NEW (require_new 新的)

比如我们设计 ServiceA.methodA() 的事务级别为 PROPAGATION_REQUIRED(需要)ServiceB.methodB() 的事务级别为 PROPAGATION_REQUIRES_NEW(新的)

那么当执行到 ServiceB.methodB() 的时候,ServiceA.methodA() 所在的事务就会挂起,ServiceB.methodB() 会起一个新的事务,等待 ServiceB.methodB() 的事务完成以后,ServiceA.methodA()才继续执行。

他与 PROPAGATION_REQUIRED (需要)的事务区别在于事务的回滚程度了。

因为 ServiceB.methodB() 是新起一个事务,那么就是存在两个不同的事务。

如果 ServiceB.methodB() 已经提交,那么 ServiceA.methodA() 失败回滚,ServiceB.methodB() 是不会回滚的。

如果 ServiceB.methodB() 失败回滚,如果他抛出的异常被 ServiceA.methodA() 捕获,ServiceA.methodA() 事务仍然可能提交(主要看B抛出的异常是不是A会回滚的异常)。

PROPAGATION_SUPPORTS(support 支持)

假设ServiceB.methodB() 的事务级别为 PROPAGATION_SUPPORTS,那么当执行到ServiceB.methodB()时,如果发现ServiceA.methodA()已经开启了一个事务,则加入当前的事务,如果发现ServiceA.methodA()没有开启事务,则自己也不开启事务。这种时候,内部方法的事务性完全依赖于最外层的事务。

PROPAGATION_NESTED(nested 嵌套)

现在的情况就变得比较复杂了, ServiceB.methodB() 的事务属性被配置为 PROPAGATION_NESTED, 此时两者之间又将如何协作呢? 
ServiceB#methodB
如果 rollback, 那么内部事务( ServiceB#methodB) 将回滚到它执行前的 SavePoint 而外部事务( ServiceA#methodA) 可以有以下两种处理方式:

a、捕获异常,执行异常分支逻辑

void methodA() { 
 
        try { 
 
            ServiceB.methodB(); 
 
        } catch (SomeException) { 
 
            // 执行其他业务, 如 ServiceC.methodC(); 
 
        } 

    }

这种方式也是嵌套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED(需要) 和 PROPAGATION_REQUIRES_NEW (新的)都没有办法做到这一点。

b、 外部事务回滚/提交 代码不做任何修改, 那么如果内部事务(ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback。

另外三种事务传播属性基本用不到,在此不做分析。

 

2:详细分析嵌套事物

什么是嵌套事务?

嵌套事务是一个外部事务的一个子事务,是一个外部事务的一个组成部分,当嵌套事务发生异常,而回滚,则会回复到嵌套事务的执行前的状态,相当于嵌套事务未执行。

如果外部事务回滚,则嵌套事务也会回滚!!!外部事务提交的时候,它才会被提交。

先说明下嵌套事物的重点:

 嵌套事务一个非常重要的概念就是内层事务依赖于外层事务

外层事务失败时,会回滚内层事务所做的动作

而内层事务操作失败并不会引起外层事务的回滚。

Savepoint点。

就是说外部事物提交,嵌套的内部事务才会提交,

外部事物回滚了,内部事务也会跟着回滚。

 

其中嵌套事务比较难理解的,难区分的两个传播属性:require_new 和 nested

1:PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 "内部" 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行. 

因为它总是开启一个新的事务,当然和原有的事务不相干了。

2:另一方面, PROPAGATION_NESTED 开始一个 "嵌套的" 事务,  它是已经存在事务的一个真正的子事务。潜套事务开始执行时,  它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint。潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。

3:由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于:

PROPAGATION_REQUIRES_NEW 完全是一个新的事务,

而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 潜套事务也会被 commit, 这个规则同样适用于 roll back. 

3:那到底什么是真正的事务嵌套呢?代码举例说明

外部事务如何利用嵌套事务的 savepoint 特性呢, 我们用代码来说话 

1. ServiceA {  
2.     /** 
3.      * 事务属性配置为 PROPAGATION_REQUIRED 
4.      */  
5.     void methodA() {  
6.         ServiceB.methodB();  
7.     }  
8. }  
9. ServiceB {  
10.     /** 
11.      * 事务属性配置为 PROPAGATION_REQUIRES_NEW 
12.      */   
13.     void methodB() {  
14.     }  
15. }     

这种情况下,因为ServiceB#methodB 方法的事务属性为require_new(总是开启一个新的事务,并挂起原有事务),

所以两者不会发生任何关系,ServiceA#methodA 和 ServiceB#methodB 不会因为对方的执行情况而影响事务的结果。

因为它们本身就是两个事务,在ServiceB#methodB 执行时 ServiceA#methodA 的事务已经挂起了。

那么nested 又怎么理解呢?

16. ServiceA {  
17.     /** 
18.      * 事务属性配置为 PROPAGATION_REQUIRED 
19.      */  
20.     void methodA() {  
21.         ServiceB.methodB();  
22.     } 
23. }  
24. ServiceB {  
25.     /** 
26.      * 事务属性配置为 PROPAGATION_NESTED 
27.      */   
28.     void methodB() {  
29.     }   
30. }     

现在的情况就变得比较复杂了, ServiceB#methodB 的事务属性被配置为 PROPAGATION_NESTED, 此时两者之间又将如何协作呢?

ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint(潜套事务中最核心的概念), 而外部事务(即 ServiceA#methodA) 可以有以下两种处理方式:

1:改写 ServiceA 如下:

1. ServiceA {  
2.     /** 
3.      * 事务属性配置为 PROPAGATION_REQUIRED 
4.      */  
5.     void methodA() {  
6.         try {  
7.             ServiceB.methodB();  
8.         } catch (SomeException) {  
9.             // 执行其他业务, 如 ServiceC.methodC();  
10.         }  
11.     }  
12. }  

这种方式也是潜套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点。

2. 代码不做任何修改, 那么如果内部事务(即 ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 

   外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback (+MyCheckedException)。

 

本文中某些是抄写网上的一些好的整理,在此汇总,也加深下自己的印象。

 

 

 

 

 

 

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这段代码定义了一个CSS选择器 `.avatar-container`,它应用于HTML中的某个元素。下面是对代码的解释: - `margin-right: 30px;`:设置元素的右外边距为30像素。 接下来是一个嵌套的选择器 `.avatar-wrapper`,它给具有 `.avatar-container` 类的元素内部的某个元素应用一些特殊样式: - `font-size: 24px;`:设置元素的字体大小为24像素。 - `color: #fdfdfd;`:设置元素的文本颜色为 `#fdfdfd`。 - `margin-top: 5px;`:设置元素的上外边距为5像素。 - `position: relative;`:将元素的定位方式设置为相对定位。 接下来是一个嵌套的选择器 `.user-avatar`,它给具有 `.avatar-wrapper` 类和 `.user-avatar` 类的元素应用一些特殊样式: - `cursor: pointer;`:将鼠标指针样式设置为手型,表示该元素可点击。 - `width: 40px;`:设置元素的宽度为40像素。 - `height: 40px;`:设置元素的高度为40像素。 - `border-radius: 10px;`:设置元素的边框半径为10像素。 - `vertical-align:middle`:使元素在垂直方向上与相邻元素居中对齐。 接下来是一个嵌套的选择器 `.el-icon-caret-bottom`,它给具有 `.avatar-wrapper` 类和 `.el-icon-caret-bottom` 类的元素应用一些特殊样式: - `cursor: pointer;`:将鼠标指针样式设置为手型,表示该元素可点击。 - `position: absolute;`:将元素的定位方式设置为绝对定位。 - `right: -20px;`:将元素相对于其定位父元素的右侧偏移20像素。 - `top: 5px;`:将元素相对于其定位父元素的顶部偏移5像素。 - `font-size: 15px;`:设置元素的字体大小为15像素。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值