highlight: androidstudio
spring事务传播,我在面试中被问到很多次,所以有必要学习一下。还有分布式事务,真的是问惨了~~~
(一) 先看下spring中有哪些事务传播级别
1. REQUIRED ( Spring默认的事务传播类型 )
- 支持当前事务,如果当前没有事务,则新建事务
- 如果当前存在事务,则加入当前事务,合并成一个事务
2. SUPPORTS
- 如果当前存在事务,则加入事务
- 如果当前不存在事务,则以非事务方式运行,这个和不写没区别
3. MANDATORY
- 如果当前存在事务,则运行在当前事务中
- 如果当前无事务,则抛出异常,即父级方法必须有事务
4. REQUIRES_NEW
- 新建事务,如果当前存在事务,则把当前事务挂起
- 这个(REQUIRES_NEW)会独立提交事务,不受调用者的事务影响,父级异常,它也是正常提交
5. NOT_SUPPORTED
- 以非事务方式运行
- 如果当前(调用者)存在事务,则把当前事务挂起
6. NEVER
- 以非事务方式运行,如果当前存在事务,则抛出异常,即父级方法必须无事务
霸道 哈哈~
7. NESTED
- 如果当前存在事务,它将会成为父级事务的一个子事务,方法结束后并没有提交,只有等父事务结束才提交
- 如果当前没有事务,则新建事务
- 如果它异常,父级可以捕获它的异常而不进行回滚,正常提交
- 但如果父级异常,它必然回滚,这也是和
REQUIRES_NEW
的主要区别 (REQUIRES_NEW是 如果父级异常的话,他任然会执行事务)
(二) 准备环境和代码 代码均上传到了github 戳这里
建两张表 DDL如下:
``sql CREATE TABLE
tablea(
idbigint NOT NULL AUTO_INCREMENT,
adescriptionvarchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
enabledtinyint NOT NULL DEFAULT '0',
cuseridint NOT NULL DEFAULT '0',
uuseridint NOT NULL DEFAULT '0',
ctimedatetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
utimedatetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (
id`) USING BTREE ) ENGINE=InnoDB AUTOINCREMENT=34 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb40900aici;
CREATE TABLE table_b
( id
bigint NOT NULL AUTOINCREMENT, b_description
varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb40900aici DEFAULT NULL, enabled
tinyint NOT NULL DEFAULT '0', cuser_id
int NOT NULL DEFAULT '0', uuser_id
int NOT NULL DEFAULT '0', ctime
datetime NOT NULL DEFAULT CURRENTTIMESTAMP, utime
datetime NOT NULL DEFAULT CURRENTTIMESTAMP ON UPDATE CURRENTTIMESTAMP, PRIMARY KEY (id
) USING BTREE ) ENGINE=InnoDB AUTOINCREMENT=35 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb40900ai_ci;
``` 创建对相应的DO mapper service 略。
(三) 用代码说话
1. REQUIRED ( Spring默认的事务传播类型 )
- 支持当前事务,如果当前没有事务,则新建事务
- 如果当前存在事务,则加入当前事务,合并成一个事务
可以看到 a和b表都没有数据,说明saveB方法中的异常不仅影响SAVEb方法,还会使得saveA方法也回滚 上个示例印证了 如果当前存在事务,则加入当前事务,合并成一个事务
下边我们把saveA方法的事务注解注释掉看看
可以看到a表成功插入,b表没有数据 (这个很好理解 因为saveA没加事务嘛) (注意此处的两个方法不再同一个类中,如果在同一个类中的话,saveB将不会生效 具体原因见 另一篇文章:
https://juejin.cn/user/1239904847403927)
这个印证了 如果当前没有事务,则自己新建一个事务
因为saveA没有事务,所以saveB新建了个事务。(注意可这里的当前可不是指saveA方法啊)
2. SUPPORTS
- 如果当前存在事务,则加入事务
- 如果当前不存在事务,则以非事务方式运行,这个和不写没区别
a表数据:
b表数据:
可以看到 a插入成功,虽然b插入失败,但是并没有回滚,异常前的那条数据依然插入成功,说明和没加事务是一样的。也就印证了 (如果当前(也就是saveA,因为我单元测试是调用的saveA方法)不存在事务,则以非事务方式运行)
而如果saveA方法上有事务且是默认传播(REQUIRED)时候 出现异常的情况下,a和b表的数据都会回滚
我们来看下saveA上有事务的情况
这个就印证了 SUPPORTS级别下 (如果当前(调用者)存在事务,则加入事务)
这个规则
我们看下如果saveA方法上的传播级别是 SUPPORTS 的话
那么数据库中
可以看到 和没加事务一样 呵呵
3. MANDATORY
- 如果当前存在事务,则运行在当前事务中
- 如果当前无事务,则抛出异常,即父级方法必须有事务
注意a插入成功,b无数据,这里b没有数据并不是事务的回滚,而是a没有事务,a的insert正常执行,而b的方法在调用时候就失败了。所以不会入库。
也就印证了 (如果当前无事务,则抛出异常,即父级方法必须有事务)
而如果在saveA方法进行事务声明,并且设置为REQUIRED,则执行saveB时就会使用saveA已经开启的事务,遇到异常就正常的回滚了. 即 (如果当前存在事务,则运行在当前事务中)
4. REQUIRES_NEW
- 新建事务,如果当前存在事务,则把当前事务挂起
- 这个(REQUIRES_NEW)会独立提交事务,不受调用者的事务影响,父级异常,它也是正常提交
从结果上来看 印证了 REQUIRES_NEW 会独立提交事务,不受调用者的事务影响,父级(调用者)异常,它也是正常提交
5. NOT_SUPPORTED
- 以非事务方式运行
- 如果当前(调用者)存在事务,则把当前事务挂起
看下结果
该场景的执行结果就是 ‘第一次插入A表描述’ 和’第二次插入B表描述‘ 没有存储,而‘第一次插入B表描述’存储成功。原因: saveA有事务,而saveB不使用事务(NOT_SUPPORTED),所以执行中saveB的第一条数据存储成功,然后抛出异常,此时saveA检测到异常事务发生回滚,但是由于saveB不在事务中,所以只有saveA的insert发生了回滚,最终只有saveB的第一个insert存储成功,而saveA的insert (回滚了)和saveB的 第二个insert(这一步根本没走到)都没有存储成功。
6. NEVER
- 以非事务方式运行,如果当前存在事务,则抛出异常,即父级方法必须无事务
霸道 哈哈~
看下结果:
7. NESTED
- 如果当前存在事务,它将会成为父级事务的一个子事务,方法结束后并没有提交,只有等父事务结束才提交
- 如果当前没有事务,则新建事务
- 如果它异常,父级可以捕获它的异常而不进行回滚,正常提交
- 但如果父级异常,它必然回滚,这也是和
REQUIRES_NEW
的主要区别 (REQUIRES_NEW是 如果父级异常的话,他任然会执行事务)
这里需要注意两点:
- 和REQUIRES_NEW的区别
REQUIRESNEW是新建一个事务并且新开启的这个事务与原有事务无关,而NESTED则是当前存在事务时(我们把当前事务称之为父事务)会开启一个嵌套事务(称之为一个子事务)。\ 在NESTED情况下父事务回滚时,子事务也会回滚,而在REQUIRESNEW情况下,原有事务回滚,不会影响新开启的事务 (REQUIRES_NEW本质是新创建一个事务,事务之间是隔离的,所以肯定不会影响啦)。
- 和REQUIRED的区别
REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一事务,那么被调用方出现异常时,由于共用一个事务,所以无论调用方是否catch其异常,事务都会回滚\ 而在NESTED情况下,被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不受影响
该场景下,所有数据都不会存入数据库,因为在saveA发生异常时,父事务回滚则子事务也跟着回滚了,可以与 REQUIRESNEW中的示例 比较看一下,就能找出与REQUIRESNEW的不同
可以看到 都没有数据 (因为父事务发生异常回滚,嵌套事务也会回滚)
将异常移到saveB中看下
将异常移到saveB中并在saveA中catch看下
看下结果
可以看到 父事务catch后,可以正常提交父事务的内容,但是嵌套事务中肯定是回滚了。
本文完 \ 明天决心把分布式事务学学,真的是被这个绊倒过很多次了。古人不是说了 ”不能被同一块石头绊倒“ 吗? 对不对?