【Spring总结】spring事务

面试题:Spring 事务失效的几种场景及原因

1、检查异常导致事务失效

(例如文件未找到这种需要去声明异常或者需要try。。catch处理的)

image-20220326192128189

原因:spring只对Error和RuntimeException这两个类及其子类才会进行回滚操作,如果抛出的是检查异常并不会进行回滚。

解决:在注解上表明回滚的异常

image-20220326192459707

  1. 抛出检查异常导致事务不能正确回滚

    原因: Spring 默认只会回滚非检查异常
    解法:配置rollbackFor属性

2,try…catch导致事务失效

image-20220326192948338

声明式事务的实现原理:

声明式事务再容器中拿得的bean并不是我们的原始目标对象,而是代理对象,代理对象在调用相关方法的时候去调用事务通知,也就是帮我们加事务的控制(是提交还是回滚),在事务通知内部才会去调用原始的目标对象,事务通知是一个环绕通知,环绕通知内部再调你的原始目标对象的方法,所以它这个是在最里面被调用的,如果最里层方法把异常捕捉住了,那外层的事务通知,他不知道这个方法是有异常的,它就提交了事务。

异常必须抛出去,对于外层的调用者才知道。不把异常抛出去,外层的事务不知道,所以会提交事务。

解决:

1.将异常抛出去

image-20220326193757214

2.告诉外层调用者回滚事务

image-20220326193943020

业务方法内自己try-catch异常导致事务不能正确回滚

原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉

解法1:异常原样抛出
解法2:手动设置TransactionStatus.setRollbackOnly()

3、切面顺序

切面类:

image-20220326194315482

要织入的方法:

image-20220326194404452

执行顺序:

先是最外层事务切面(环绕通知), 中间一层是自定义的切面,最里层是目标方法。

事务失效分析:

目标方法抛出异常,这个异常先抛给中间一层的自定义的切面。中间自定义的切面中try…catch了,没有将异常抛给外层切面事务。

解决方法和上面的2是一样的。要么将异常抛出,要么调用setRollbackOnly()方法。

解决方法三:交换事务切面和自定义切面的顺序,使用order,数字越小的优先级越高。(浓缩的都是精华)

spring的是事务切面的order为最大的整数值,也就是说优先级最低。

image-20220326195745710

我们的目标方法抛出异常后,被里层的事务切面拿到,进行事务回滚。

总结:我们处理异常的时候尽量使用抛出异常的方式,让spring事务进行回滚。

  1. aop 切面顺序导致导致事务不能正确回滚

原因:事务切面优先级最低,但如果自定义的切面优先级和他一样,则还是自定义切面在内层,这时若自定义切面没有正确抛出异常…

解决同2

4、非public方法

@Transactional注解必须加在公共方法上,否则形同虚设

image-20220326200313307

总结

image-20220326200727306

5、父子容器

添加controller方法调用service

image-20220326200820660

image-20220326201417823

对于controller和service我们可以将它们配置在同一个容器当中,也可以将它们配置在父子容器中。

将service、mapper、数据源放在父容器当中,controller放在子容器当中。

image-20220326201137224

父子容器设置完了有什么用?

子容器找某个bean的时候,比如子容器要用到service,如果子容器里面没有这个service,那它就回到父容器里面去找,完成依赖注入。

发现在controller调service方法的时候根本就没有加上事务。

原因:子容器里面包扫描的范围太大了,将service也扫描进来了,但是我们子容器里没有配置声明式事务。这个时候controller注入的是子容器里面的service。

只有在父容器里面才配置了@EnableTransactionManagement。

解决:

不要让子容器controller扫描到他不该扫描到的包,只扫描controller所在的包即可。

6、本类方法调用(传播行为失效)

image-20220326202233730

传播行为:

REQUIRED:如果之前没有事务,那它就会创建新的事务。如果有了事务,那就加入已有的事务。REQUIRES_NEW:不管之前有没有事务,都会创建一个新的事务。

两个方法相互调用,bar方法会创建一个新的事务,所以应该会有两个事务。

image-20220326202753853

发现只有一个事务,两个方法在一个事务中。

问题出在事务的代理上。因为事务的增强功能只有通过代理去调用这个方法,才会代理调用我们的事务通知,事务通知再调目标,才具备事务的增强。

image-20220326203055618

但是在foo方法的内部,需要间接调用bar方法的时候。bar()方法不是通过代理调用的,不会进行功能的增强。

解决:

让bar()方法调用的时候也要通过代理对象去调用才可以。

1、自身注入自身(循环依赖)

image-20220326203508154

image-20220326203639241

2、通过AopContext(aop上下文)得到当前代理对象(要设置expose=true)

image-20220326203827456

image-20220326203920935

上面两种解决方法都是获取代理对象实现功能增强。

image-20220326204034570

7、原子性失效

image-20220327102039070

多线程下这段转帐代码是有问题的。

使用CountDownLatch进行多线程测试

image-20220327102301248

先让主线程等待(latch.await()),当线程1和2都执行完了,主线程恢复向下运行。

发现一号账户转成了负数,出现了问题。

多线程下会出现指令交错,

image-20220327102805993

会想到在方法上加synchronized加锁,但还是有问题。

image-20220327103201470

image-20220327103453556

即在方法中的操作执行完了(三条数据库操作),这个时候synchronized就会把锁释放,但是这个时候事务还没提交,此刻另外一个线程抢到了锁,在对其进行操作,就会出现问题。

解决:

1、在调用转帐方法的时候加锁,扩大锁的范围

image-20220327103941181

它就不仅包括目标方法的调用,还有代理方法,以及中间事务通知包括目标方法。他们的调用都是原子的。

锁的位置不能加在目标方法上,要加就要加在代理方法的调用上

2、利用数据库加锁和事务配合使用

select不会阻塞,但是可以修改让其加锁阻塞

image-20220327104421384

image-20220327104714662

如果你把synchronized加载目标方法上,并不能保证原子性,因为目标方法里仅包含了sql语句的操作,最后的提交操作是在通知里完成的。加在目标方法上的synchronized并不能将通知上的提交操作也保护起来

可以修改让其加锁阻塞

[外链图片转存中…(img-3W6Re8cG-1648384749282)]

[外链图片转存中…(img-n97p7q61-1648384749283)]

如果你把synchronized加载目标方法上,并不能保证原子性,因为目标方法里仅包含了sql语句的操作,最后的提交操作是在通知里完成的。加在目标方法上的synchronized并不能将通知上的提交操作也保护起来

image-20220327105129784

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值