栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【Spring总结】spring事务

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【Spring总结】spring事务

面试题:Spring 事务失效的几种场景及原因 1、检查异常导致事务失效

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

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

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

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

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

2,try…catch导致事务失效

声明式事务的实现原理:

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

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

解决:

1.将异常抛出去

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

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

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

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

3、切面顺序

切面类:

要织入的方法:

执行顺序:

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

事务失效分析:

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

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

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

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

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

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

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

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

解决同2

4、非public方法

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

总结

5、父子容器

添加controller方法调用service

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

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

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

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

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

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

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

解决:

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

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

传播行为:

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

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

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

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

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

解决:

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

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

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

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

7、原子性失效

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

使用CountDownLatch进行多线程测试

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

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

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

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

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

解决:

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

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

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

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

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

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

可以修改让其加锁阻塞

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

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

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/782189.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号