AQS到底有什么用?难道就真的只是为了面试吗?
当然不是说AQS没用,如果你不是做基础架构或者中间件开发,你很难感受到AQS的威力。当然,学习很多时候,需要的是正向^ M ) u ? |反馈,学了太多造火箭的东西,面试完就再也用不上,自然| I V S { m很难有动力保持持续学习。那么,有没有受众群体大,就算平时CRUD的同学也用得到,同时面试又喜欢问,最重要的是,出问题的时候,搜索还不容易搜索出答案的知识点?
那么,这个就是肥朝之前提到的Spring事务传播机制。
为啥是Spring事务传播机制?
原因很简单,因为能满足上s # a L . 2 F P述三个条件的,第一个想到的,就是Spring事务传播机制,他具备了几个条件
1.CRUD的同学,平时和事s 9 n c z A务打交道最多。并且,事务一旦出了问题,那可是爆炸性的E ! 5伤害,这点毋容置疑
2.面试高频考! | B c 6 e #点,一般还会问数据库的隔离级别。但是肥朝发现,很多同学把数据库的隔离级别和Spring的事务传播机制这两) ! E # A个概念搞混,其实这是两码事。同时,也有比较出名的面试题,\"做51次操作,前面50次成功,第51次失败,如何把前面成功的50次提交,第51次失败的回滚\"。如果你对Spring的事务传播机制不了解,那么你对于这个问题,是没有什么头绪的,原因在于,很多同学平时只处理过,全部提交和全部回滚两个情况。
3.即使你在? d # x别的地方看过类似的Spring事务传播机制的文章,对于常规的情况是没问题,但是如果在多个try模5 Y g l型下,你对于是否能回滚,^ Q Q 5 s m | 1心里还是没底的,说白了,就是你还是没能摸透原理!
4.最重要的当然是肥朝之前答应过大家要写这篇,不想做渣男。e F r
基本概念
Spring的事务传播机制有以下七种
PROPAGATION_REQUIRED:Spring的默认传播级别,如果上下文中存在事务则加入当前事务,如果不存在事务则新建事务执行。
PROPAGATION_SUPPORTm I * O E 9 oS:如果上下文中存在事务则加入当前事务,如果没有事务则以非事务方式执行。
PROPAGATION_MANDATORY:该传播级别要求上下文中必须存在事务,否则抛出异常。
PROPAGATION_REQUIRES_NEW:该传播级别每次执行都会创建新事务,并同时将上c k f q 3 7 ) z b下文中的事务挂起,执行完当前线程后再恢复上下文中事务。* / } = $ 6 L = x(子事务的执行结果不影响父事务的执行和回滚)
PROPAGATION_NOT_SUPPORTED:当上下文中有事务则挂起当前事务,执行完当前逻辑后再恢复上下文事务。(降低事务大小,将非核心的执行逻辑包裹执行。)
PROPAGATION_NEVER:该传播级别要求上下文中不能存在事务,否则抛出异常。
PROPAGATION_NESTED:嵌套事务,如果上下文中存在事务则% 4 4 ] #嵌套执行,如果不存在s k ]则新建事务。(save point概* K ] [念)
看完这七种是不是云里雾里?那就对了!坦白说,这七种你记得住,其实也没多F / }大意义,记得住多半也是背下来的。其中PROPAGATION_REQUIRED这种默认的情况,是我们用得最多的,基本覆盖90%的情况,也就是大家常见的,一起回滚的情l o Q D y况。还有一个是PROPAGATION_REQUIRES_NEW。基本你把这两个掌握了,应对95%的情况一点儿问题都没有,如果还有问题,你再来查这七种,找到你合适的。
例题讲解
如果只是罗列概念,那意义不大,因此我们采用应试教育的方式来做题,才是检验掌握程度比较好的做法
案例一:常规情况
这种也是大家最常见的情况D w % ~ D r k G o
@Transactiok = Fnal
@Override
public void Example1(User user) {
use{ p B nrMapper.insert(user);
propagationService.red ; / w 0 ( X X kquired();
}
@TransacI 3 C O K r $ k Vtio( # d 5 D ;nal
@Override
p2 g vublic void required() {
throw new NullPointerException;
}
单元测试
@Test
public void tes[ ? v +tExampx S u w u Xle1() throws Exception {
User user = new U5 { ! D d #ser();
user.setName;
userService.Example1(user);
}
案例二:try-requireJ $ T i * { Q 8d
开始敲黑板划重点了,这个情况也是大家最常见的情况之一,但是由于这个try起来了,异常不会抛出,那么,这个insert能否插入数据呢?
@Transactional
@Override
public void Example2(User user) {
userMapper.insert(= u o ^ N juser);
trI w Qy {
propagationService.required();
}p e ) e n c$ N 3 ( M & f %atch (Exception e) {
e.printStackTrace();
}
}
@TransactiH O o C Oonal
@Override
public void required() {
t_ G U 5 M q m ;hrow ne y y = wew NullPointerException;
}
单元测试
@Test
public void testExample2() throws Exception {
User user = new UsS E # 3 _ Mer()r 8 H N L;
user.setName;
us@ ; 7 L n m E rerService.Example2(user)l l Y T i;
}
案例三:try-requiresNew
这个案例和上面的案例很相似,区别在于,这里的隔离级别是Propagation.REQUIRES_I v - * y wNEW,那么,这个insert能否插入数据呢?
@Transactional
@Override
public void Examplp ] } [ ~ | ( i _e3(User user) {
userM, a R j Happer~ 2 p F p l r K.insert(user);
try {
propagationService.requiresNew();
} catch (Exception e) {
e.printStackTrace();
}
}
@Transactional(propagatiog @ Jn = Propagatm Xion.REQUIRES_NEW)
@q * ~ pOverr9 ) Hide
public void requiresNew() {
throw new NullPointerException;
}
单元测试
@Test
public void testExample3() thro+ 8 P m M $ 5 K yws Exception {
User user = new User();
user.setName;
userSe_ F S Mrb h 5 h ? { / 8vice.Example3(user);
}
案例四:常规情况
这个和案例一很想,结果会有差别吗?最后insert能插入吗?
@Transactional
@Override
public void Example4(User user) {
userMapper.insert(user);
propagationSL b service.requiresNew();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)x p p
@Override
public void requiresNew() {
throw new NullPoint ~ X | m 5terException;
}
单元测试
@Test
public void testExample4() throws Exception {
User user = new User();
user.setName;
usea x B | lrServiceG v ^ s 6 ~ l D 8.Example4(user);
}
解密及大白话说明原理) m P m M d +
案例一
这个不用说,稍微有点Java常识g | : 6 ; p K W z的人都知道,异常必然会导致回滚,数据库不会插入数据。
案例二
这个到底会不会插入数据呢?毕竟这个异常被try起来了。这个时候,正常的思维都会认为,能正常插入数据,但是答案是,不会插入数据,并且抛出异常
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back becB P o [ naus R L we it has been marked as rollback-only
为啥会这样呢?
案例三
这个和案例二很像,因为有了案例二的阴影,这个时候你就变得不确定了。答案是,) q D O X } 6能正常插入数据。
案例四
这个和案例一很像,本来你是很确定能不能插入数据的,但是有了案例三的阴影之后,这个时候你又变得不确定了。答案是,不会插入数据。
原理!
这四个案例,只要你把这四个案例和` Y 8 $ m R _原理弄熟,再复杂的各种try模型下,事务是否回滚,你都清清楚楚。S C 4 0 P
我们先来看看@Transactional的核心方法
if (txAttr ==D ^ Q r S : V 5 q null || !(t1 x em instanceof CallbackPreferringPlatformTransactionManager)) {
// 开启事务
TransactionInfo txI- w 8 O : Q ` , Info = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// 执行业务方法
retVal = invocation.proceedWithInvocation();
}
catch (ThrowabP * l I Wle ex) {
// 回滚事务
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
// 提交事务
commitTp V g H 8ransactioN s y QnAfterReturn H H ring(txInfo);
return retVal;
}
我们重点解析案例二和案例三的情况。我们再把那两个事务传播机制的意思来解读一下:
PROPAGATION_REQg | q % 4 C 7 0UIRED:Spring的默认传播级别,如果上下文中存在事务则加入当前事务,如果不存在事务则新建事务_ f W ? d执行。
PROPAGATION_REw M k J aQUIRES_NEW:该传播级别每次执行都会创建新事务,并同时将上下文中的事务挂起,执行完当前线程后再恢复上下文中事务。
首先来看案例二,执行Example2方法的时候,由上文得知,将开启一个事务,再执行到required方法时,此时,因为~ ] 7 H } 5 [用得的默认的隔离级别,因此,这个时候,会加6 5 y @ B l T }入到刚才的事务之中,然后required方法中,出现了异常,我们来看
completeTransactionAfterThrowing(txInfo, ex);
中的i & g核心方法
从doSetRollbackOnly(status)这9 k z个单词就知道,required的时候,已经把这个事务设置成Rolld c b X $ # J ~backOnlyb ^ 1 9 P,因此,虽然try% N O p f ?住了,但是Example2执行完提交的时候,却发现无法提交,所以异常信息如下:
Transaction rolled back because it has been marked as rollback-only
一图胜千言,我用一张图来描述这个关系
那为啥案例三,又能插入数X ~ M据呢?还是用一张R 4 , { U ` I . ]图来描述
需要注意
@Transactional有很多注意点
- 在同个类中调v b 6 W用A方法调用B方法,B方法是不会开启事务,自然也就不会用到事务的传播机制。这个原理后续肥朝会解析,当然如果你连这句话I L H都不知道是什么意思,假粉实锤了!
- @Transactional默认情况下i W b - x C,只回滚RuntimeException。如果你抛出的异常不是RuntimeException,可能导致在默认情况下和本文有所偏差
当然,这么多注意点,哪记得住,因此,留意后续的原理解析,就非常有必要了。
写在最后
欢迎大家关注我的公种浩【java耕耘者】,文章都会在里面更新,整理的资料也会放在里面。
好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是人才。