原创 | CRUD更要知道的Spring事务传播机制

前言

你们好,我是肥朝。前阵子看到这么一条粉丝的朋友圈php

AQS到底有什么用?难道就真的只是为了面试吗?面试

图片

固然不是说AQS没用,若是你不是作基础架构或者中间件开发,你很难感觉到AQS的威力。固然,学习不少时候,须要的是正向反馈,学了太多造火箭的东西,面试完就再也用不上,天然很难有动力保持持续学习。那么,有没有受众群体大,就算平时CRUD的同窗也用获得,同时面试又喜欢问,最重要的是,出问题的时候,搜索还不容易搜索出答案的知识点?(肥朝的老粉丝都知道,这最后一点对于肥朝来讲很是重要)spring

那么,这个就是肥朝以前提到的Spring事务传播机制数据库

为啥是Spring事务传播机制?

缘由很简单,由于能知足上述三个条件的,肥朝第一个想到的,就是Spring事务传播机制,他具有了几个条件微信

1.CRUD的同窗,平时和事务打交道最多。而且,事务一旦出了问题,那但是爆炸性的伤害,这点毋容置疑架构

2.面试高频考点,通常还会问数据库的隔离级别。可是肥朝发现,不少同窗把数据库的隔离级别Spring的事务传播机制这两个概念搞混,其实这是两码事。同时,也有比较出名的面试题,"作51次操做,前面50次成功,第51次失败,如何把前面成功的50次提交,第51次失败的回滚"。若是你对Spring的事务传播机制不了解,那么你对于这个问题,是没有什么头绪的,缘由在于,不少同窗平时只处理过,所有提交和所有回滚两个状况。app

3.即便你在别的地方看过相似的Spring事务传播机制的文章,对于常规的状况是没问题,可是若是在多个try模型下,你对因而否能回滚,内心仍是没底的,说白了,就是你仍是没能摸透原理!ide

4.最重要的固然是肥朝以前答应过你们要写这篇,不想作渣男。单元测试

基本概念

Spring的事务传播机制有如下七种学习

PROPAGATION_REQUIRED:Spring的默认传播级别,若是上下文中存在事务则加入当前事务,若是不存在事务则新建事务执行。

PROPAGATION_SUPPORTS:若是上下文中存在事务则加入当前事务,若是没有事务则以非事务方式执行。

PROPAGATION_MANDATORY:该传播级别要求上下文中必须存在事务,不然抛出异常。

PROPAGATION_REQUIRES_NEW:该传播级别每次执行都会建立新事务,并同时将上下文中的事务挂起,执行完当前线程后再恢复上下文中事务。(子事务的执行结果不影响父事务的执行和回滚)

PROPAGATION_NOT_SUPPORTED:当上下文中有事务则挂起当前事务,执行完当前逻辑后再恢复上下文事务。(下降事务大小,将非核心的执行逻辑包裹执行。)

PROPAGATION_NEVER:该传播级别要求上下文中不能存在事务,不然抛出异常。

PROPAGATION_NESTED:嵌套事务,若是上下文中存在事务则嵌套执行,若是不存在则新建事务。(save point概念)

看完这七种是否是云里雾里?那就对了!坦白说,这七种你记得住,其实也没多大意义,记得住多半也是背下来的。其中PROPAGATION_REQUIRED这种默认的状况,是咱们用得最多的,基本覆盖90%的状况,也就是你们常见的,一块儿回滚的状况。还有一个是PROPAGATION_REQUIRES_NEW。基本你把这两个掌握了,应对95%的状况一点儿问题都没有,若是还有问题,你再来查这七种,找到你合适的。

例题讲解

若是只是罗列概念,那意义不大,所以咱们采用应试教育的方式来作题,才是检验掌握程度比较好的作法

案例一:常规状况

这种也是你们最多见的状况

@Transactional
@Override
public void Example1(User user) {
   userMapper.insert(user);
   propagationService.required();
}
@Transactional
@Override
public void required() {
   throw new NullPointerException("肥朝伪装抛出了异常");
}

单元测试

@Test
public void testExample1() throws Exception {
   User user = new User();
   user.setName("微信公众号:肥朝");
   userService.Example1(user);
}

案例二:try-required

开始敲黑板划重点了,这个状况也是你们最多见的状况之一,可是因为这个try起来了,那么,这个insert可否插入数据呢?

@Transactional
@Override
public void Example2(User user) {
   userMapper.insert(user);
   try {
       propagationService.required();
   } catch (Exception e) {
       e.printStackTrace();
   }
}
@Transactional
@Override
public void required() {
   throw new NullPointerException("肥朝伪装抛出了异常");
}

单元测试

@Test
public void testExample2() throws Exception {
   User user = new User();
   user.setName("微信公众号:肥朝");
   userService.Example2(user);
}

案例三:try-requiresNew

这个案例和上面的案例很类似,区别在于,这里的隔离级别是Propagation.REQUIRES_NEW,那么,这个insert可否插入数据呢?

@Transactional
@Override
public void Example3(User user) {
   userMapper.insert(user);
   try {
       propagationService.requiresNew();
   } catch (Exception e) {
       e.printStackTrace();
   }
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void requiresNew() {
   throw new NullPointerException("肥朝伪装抛出了异常");
}

单元测试

@Test
public void testExample3() throws Exception {
   User user = new User();
   user.setName("微信公众号:肥朝");
   userService.Example3(user);
}

案例四:常规状况

这个和案例一很想,结果会有差异吗?最后insert能插入吗?

@Transactional
@Override
public void Example4(User user) {
   userMapper.insert(user);
   propagationService.requiresNew();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void requiresNew() {
   throw new NullPointerException("肥朝伪装抛出了异常");
}

单元测试

@Test
public void testExample4() throws Exception {
   User user = new User();
   user.setName("微信公众号:肥朝");
   userService.Example4(user);
}

解密及大白话说明原理

案例一

这个不用说,稍微有点Java常识的人都知道,异常必然会致使回滚,数据库不会插入数据。

案例二

这个到底会不会插入数据呢?毕竟这个异常被try起来了。这个时候,正常的思惟都会认为,能正常插入数据,可是答案是,不会插入数据,而且抛出异常

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

为啥会这样呢?

案例三

这个和案例二很像,由于有了案例二的阴影,这个时候你就变得不肯定了。答案是,能正常插入数据。

案例四

这个和案例一很像,原本你是很肯定能不能插入数据的,可是有了案例三的阴影以后,这个时候你又变得不肯定了。答案是,不会插入数据。

原理!

这四个案例,通过肥朝精心挑选,只要你把这四个案例和原理弄熟,再复杂的各类try模型下,事务是否回滚,你都清清楚楚。因此你觉得叫你关注肥朝的公众号是害你吗?是爱你啊老哥!

咱们先来看看@Transactional的核心方法

if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// 开启事务
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// 执行业务方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 回滚事务
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
// 提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}

咱们重点解析案例二和案例三的状况。咱们再把那两个事务传播机制的意思来解读一下:

PROPAGATION_REQUIRED:Spring的默认传播级别,若是上下文中存在事务则加入当前事务,若是不存在事务则新建事务执行。

PROPAGATION_REQUIRES_NEW:该传播级别每次执行都会建立新事务,并同时将上下文中的事务挂起,执行完当前线程后再恢复上下文中事务。

首先来看案例二,执行Example2方法的时候,由上文得知,将开启一个事务,再执行到required方法时,此时,由于用得的默认的隔离级别,所以,这个时候,会加入到刚才的事务之中,而后required方法中,出现了异常,咱们来看

completeTransactionAfterThrowing(txInfo, ex);

中的核心方法

doSetRollbackOnly(status)这个单词就知道,required的时候,已经把这个事务设置成RollbackOnly,所以,虽然try住了,可是Example2执行完提交的时候,却发现没法提交,因此异常信息以下:

Transaction rolled back because it has been marked as rollback-only

一图胜千言,我用一张图来描述这个关系

那为啥案例三,又能插入数据呢?仍是用一张图来描述

须要注意

@Transactional有不少注意点

  • 在同个类中A方法调用B方法,B方法是不会开启事务,天然也就不会用到事务的传播机制。这个原理后续肥朝会解析,固然若是你连这句话都不知道是什么意思,假粉实锤了!

  • @Transactional默认状况下,只回滚RuntimeException。若是你抛出的异常不是RuntimeException,可能致使在默认状况下和本文有所误差

固然,这么多注意点,哪记得住,所以,留意后续的原理解析,就很是有必要了。

写在最后

什么?你也想亲自体验一下从原理源码解决问题的过程?没问题,肥朝已经把demo给你准备好了,添加肥朝微信好友(微信号:feizai_php),【无套路】分享地址告诉你。



图片

相关文章
相关标签/搜索