一篇让你学会 11个Spring 失效场景

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一篇让你学会 11个Spring 失效场景相关的知识,希望对你有一定的参考价值。

参考技术A 其实关于spring事务失效的场景,网络上文章介绍的不少,参差不齐。这里只分享下自己的见解,时长大概10分钟左右,先上个图介绍下。

事务方法需要定义public,非public方法事务会失效。事务拦截器TransactionalInterceptor会在执行方法前进行拦截,通过动态代理方式如果是cglib就是intercept方法或者jdk的invoke方法间接调用AbstractFallbackTransactionAttributeSource类的getTransactionAttribute方法获取配置信息,附上源码图:

进一步的跟踪getTransactionAttribute方法,我们就能看到,spring对于非public修饰的方式,返回的事务对象是null,其中allowPublicMethodsOnly返回的是一个布尔false。

事务底层使用了aop,那么也就是说通过jdk或者是cglib生成代理类,在代理类中实现的事务的功能,如果说方法是final修饰的了,那么就会导致代理类中无法重写该方法,从而导致添加事务失败。同样的如果是static的修饰的话也是无法通过动态代理变成事务方法。

简单来说就是一个方法内部调用另一个方法,但是另一个方式是有事务的,这样也会导致事务失效,因为这个调用的是this对象的方法,而不是另一个方法持有的对象,可以这里理解。

如果想要在方法内部调用另一个方法也有事务的话,就需要新建一个service对象持有。

这样,通过新建一个service方法,将事务添加到新建的service方法里就可以了。说到这里可能小伙伴觉得这样有点麻烦,那么是否有没有其他的方式不新建一个方法呢,答案是可以的,就是注入自己,利用了spring ioc内部的三级缓存的机制,这里注入自己就很好的保证了也不会出现循环依赖:

其实到了这一步,还是发现有点不太雅观,并不是说上面代码有什么问题只是觉得,可以让上面代码更加好看一点,那么有没有呢,答案是有的,是什么呢?这就不得不佩服spring强大完善的支持,那就是AopContext.currentProxy(),这个就是创建代理类,在方法调·调用前后切入,这个代理类对象是保存在ThreadLocal中的,所以通过这个代理类对象调用事务方法就能生效了。

这样看来,代码是不是就优雅多了,哈哈!!!

这里需要明确一个前提,就是使用spring事务的前提,就是对象要被spring管理就需要创建bean实例,在开发中,我们都是通过@Controller,@Service,@Component,@Repository等注解自动的实现依赖注入实例化的功能,但假如说在相应的控制层,业务层,数据层忘记加相应的注解,那么也是会失效的。因为没有交给spring管理,例如:

回想起前几年配置事务管理器时,都会有这样的一段配置:

通过这一段配置也可以知道,其实spring事务就是通过数据库连接事务多线程连接会导致持有的connetion不是同一个,从网上找了一张图,通过这张图进一步理解:

接着附上伪代码结合上面的图进一步理解:

事务add方法中调用了另一个事务doOtherThing,但是事务方法是在另一个线程中调用的,这样就会导致两个方法不在同一个线程中,获取到的数据库链接不一样,是两个不同的事务,一旦doOtherThing发生异常,add方法也是不可能发生回滚的.这里需要解释以下什么是同一个事务,也就是说只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。

这个就没什么好讲的了,也就是innodb和myisam引擎的不同,5版本以前默认是myisam引擎,这个引擎是不支持事务的,5版本以后的innodb是支持事务的。

这个其实可能也是比较容易忽略的,因为我们印象里好像没怎么配置过怎么开启事务,也确实是这样哈,为什么?其实原因很简单,springboot项目通过DataSourceTransactionManagerAutoConfiguration这个类已经默默的为我们开启了事务。

这个类会加载spring.datasource这个配置文件从而启动事务,如果是非springboot项目就需要自己手动在xml文件中配置事务管理器。

类似这样的从而开启事务。

在使用@Transactional注解时,是可以指定propagation参数的,该参数是用来指定事务的传播特性,其中只有required,requires_new,nested这三种才会创建新事务:

像上面的Propagation.NEVER这种类型的传播特性不支持事务,如果有事务则会抛异常。

像这种手动try...catch了异常,又没有手动抛出,那么sring就会认为程序是异常的就不会回滚了。

捕获了异常又抛出了exception异常,事务同样不会回滚,因为spring事务默认情况下只会回滚RuntimeException(运行时异常)和Error(错误),对于普通的Exception(非运行时异常),不会回滚,网上找了一张图:

这里exception里除了分为运行时异常和非运行时异常(ioException)。

1) 让checked例外也回滚:

在整个方法前加上 @Transactional(rollbackFor=Exception.class)

2) 让unchecked例外不回滚:

@Transactional(notRollbackFor=RunTimeException.class)

3)不需要事务管理的(只查询的)方法:@Transactional(propagation=Propagation.NOT_SUPPORTED)

这里需要提及的一句是,如果是自定义了的异常,比如说我自定义了DALException异常,那么就应该是@Transactional(notRollbackFor=DALException.class),一旦抛出的异常不属于DALException异常,那么事务也是不会生效的。

其实这个就有点像是js里的冒泡事件,可能我只是需要底部,结果外层窗口事件也触发了,联想到事务这里,那么也是一样的,嵌套多个可能只是想回滚对应的事务,就不用把其他事务也回滚了,这个可以通过try...catch来处理,将需要处理回滚的事务放这里面就不会把外层的也会滚了。

以上是关于一篇让你学会 11个Spring 失效场景的主要内容,如果未能解决你的问题,请参考以下文章

一篇让你搞懂 Nginx

spring事务失效的几种场景以及原因

一篇让你能够玩转 Nginx 的文章!

一篇让你上手mysql安装以及my.ini配置

从 倔强青铜 到 荣耀王者!只差这篇让你学会Unity中最重要的部分——脚本组件✨

一篇让你明白进程与线程之间的区别与联系