一篇让你学会11个Spring失效场景
其实关于spring事务失效的场景,网络上文章介绍的不少,参差不齐。这里只分享下自己的见解,时长大概10分钟左右,先上个图介绍下。
1。访问权限问题
事务方法需要定义public,非public方法事务会失效。事务拦截器TransactionalInterceptor会在执行方法前进行拦截,通过动态代理方式如果是cglib就是intercept方法或者jdk的invoke方法间接调用AbstractFallbackTransactionAttributeSource类的getTransactionAttribute方法获取配置信息,附上源码图:
进一步的跟踪getTransactionAttribute方法,我们就能看到,spring对于非public修饰的方式,返回的事务对象是null,其中allowPublicMethodsOnly返回的是一个布尔false。
2。方法被final修饰
事务底层使用了aop,那么也就是说通过jdk或者是cglib生成代理类,在代理类中实现的事务的功能,如果说方法是final修饰的了,那么就会导致代理类中无法重写该方法,从而导致添加事务失败。同样的如果是static的修饰的话也是无法通过动态代理变成事务方法。3。方法内部调用
简单来说就是一个方法内部调用另一个方法,但是另一个方式是有事务的,这样也会导致事务失效,因为这个调用的是this对象的方法,而不是另一个方法持有的对象,可以这里理解。
如果想要在方法内部调用另一个方法也有事务的话,就需要新建一个service对象持有。ServicepublicclassTmTrimServicebackImpl{publicvoidgetById(Longid){TmTrimServiceliImpl。getTrimById(id);}}ServicepublicclassTmTrimServiceliImpl{Transactional(rollbackForException。class)publicvoidgetTrimById(Longid){TmTrimVOtmTrimVOnewTmTrimVO();}}
这样,通过新建一个service方法,将事务添加到新建的service方法里就可以了。说到这里可能小伙伴觉得这样有点麻烦,那么是否有没有其他的方式不新建一个方法呢,答案是可以的,就是注入自己,利用了springioc内部的三级缓存的机制,这里注入自己就很好的保证了也不会出现循环依赖:ServicepublicclassTmTrimServicebackImpl{AutowiredprivateTmTrimServicebackImpltmTrimServicebackImpl;publicvoidgetById(Longid){tmTrimServicebackImpl。getTrimById(id);}Transactional(rollbackForException。class)publicvoidgetTrimById(Longid){TmTrimVOtmTrimVOnewTmTrimVO();}}
其实到了这一步,还是发现有点不太雅观,并不是说上面代码有什么问题只是觉得,可以让上面代码更加好看一点,那么有没有呢,答案是有的,是什么呢?这就不得不佩服spring强大完善的支持,那就是AopContext。currentProxy(),这个就是创建代理类,在方法调调用前后切入,这个代理类对象是保存在ThreadLocal中的,所以通过这个代理类对象调用事务方法就能生效了。ServicepublicclassTmTrimServicebackImpl{publicvoidgetById(Longid){((TmTrimServicebackImpl)AopContext。currentProxy())。getTrimById(id);}Transactional(rollbackForException。class)publicvoidgetTrimById(Longid){TmTrimVOtmTrimVOnewTmTrimVO();}}
这样看来,代码是不是就优雅多了,哈哈!!!4。未被spring事务管理
这里需要明确一个前提,就是使用spring事务的前提,就是对象要被spring管理就需要创建bean实例,在开发中,我们都是通过Controller,Service,Component,Repository等注解自动的实现依赖注入实例化的功能,但假如说在相应的控制层,业务层,数据层忘记加相应的注解,那么也是会失效的。因为没有交给spring管理,例如:
5。多线程调用
回想起前几年配置事务管理器时,都会有这样的一段配置:
通过这一段配置也可以知道,其实spring事务就是通过数据库连接事务多线程连接会导致持有的connetion不是同一个,从网上找了一张图,通过这张图进一步理解:
接着附上伪代码结合上面的图进一步理解:Slf4jServicepublicclassUserService{AutowiredprivateUserMapperuserMapper;AutowiredprivateRoleServiceroleService;Transactionalpublicvoidadd(UserModeluserModel)throwsException{userMapper。insertUser(userModel);newThread((){roleService。doOtherThing();})。start();}}ServicepublicclassRoleService{TransactionalpublicvoiddoOtherThing(){System。out。println(保存role表数据);}}
事务add方法中调用了另一个事务doOtherThing,但是事务方法是在另一个线程中调用的,这样就会导致两个方法不在同一个线程中,获取到的数据库链接不一样,是两个不同的事务,一旦doOtherThing发生异常,add方法也是不可能发生回滚的。这里需要解释以下什么是同一个事务,也就是说只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。6。多线程调用
这个就没什么好讲的了,也就是innodb和myisam引擎的不同,5版本以前默认是myisam引擎,这个引擎是不支持事务的,5版本以后的innodb是支持事务的。7。未开启事务
这个其实可能也是比较容易忽略的,因为我们印象里好像没怎么配置过怎么开启事务,也确实是这样哈,为什么?其实原因很简单,springboot项目通过DataSourceTransactionManagerAutoConfiguration这个类已经默默的为我们开启了事务。
这个类会加载spring。datasource这个配置文件从而启动事务,如果是非springboot项目就需要自己手动在xml文件中配置事务管理器。beanclassorg。springframework。jdbc。datasource。DataSourceTransactionManageridtransactionManagerpropertynamedataSourcerefdataSourcepropertybean
类似这样的从而开启事务。8。错误的传播特性
在使用Transactional注解时,是可以指定propagation参数的,该参数是用来指定事务的传播特性,其中只有required,requiresnew,nested这三种才会创建新事务:ServicepublicclassUserService{Transactional(propagationPropagation。NEVER)publicvoidadd(UserModeluserModel){saveData(userModel);updateData(userModel);}}
像上面的Propagation。NEVER这种类型的传播特性不支持事务,如果有事务则会抛异常。9。自己吞了异常Slf4jServicepublicclassUserService{Transactionalpublicvoidadd(UserModeluserModel){try{saveData(userModel);updateData(userModel);}catch(Exceptione){log。error(e。getMessage(),e);}}}
像这种手动try。。。catch了异常,又没有手动抛出,那么sring就会认为程序是异常的就不会回滚了。10。手动抛了别的异常Slf4jServicepublicclassUserService{Transactionalpublicvoidadd(UserModeluserModel)throwsException{try{saveData(userModel);updateData(userModel);}catch(Exceptione){log。error(e。getMessage(),e);thrownewException(e);}}}
捕获了异常又抛出了exception异常,事务同样不会回滚,因为spring事务默认情况下只会回滚RuntimeException(运行时异常)和Error(错误),对于普通的Exception(非运行时异常),不会回滚,网上找了一张图:
这里exception里除了分为运行时异常和非运行时异常(ioException)。
1)让checked例外也回滚:
在整个方法前加上Transactional(rollbackForException。class)
2)让unchecked例外不回滚:
Transactional(notRollbackForRunTimeException。class)
3)不需要事务管理的(只查询的)方法:Transactional(propagationPropagation。NOTSUPPORTED)
这里需要提及的一句是,如果是自定义了的异常,比如说我自定义了DALException异常,那么就应该是Transactional(notRollbackForDALException。class),一旦抛出的异常不属于DALException异常,那么事务也是不会生效的。11。嵌套事务回滚多了
其实这个就有点像是js里的冒泡事件,可能我只是需要底部,结果外层窗口事件也触发了,联想到事务这里,那么也是一样的,嵌套多个可能只是想回滚对应的事务,就不用把其他事务也回滚了,这个可以通过try。。。catch来处理,将需要处理回滚的事务放这里面就不会把外层的也会滚了。
原文链接:https:developer。51cto。comarticle703239。html?utmsourcetuicoolutmmediumreferral
与其抱怨生活,不如改变心态人活着总要经历一定的委屈。有委屈就容易产生埋怨。埋怨自己的怀才不遇,其实可能是努力还不够,才华还不够足够的出众、优秀;埋怨世态炎凉、人走茶凉,其实不过是你的私心太重……
NASA开始检查刚从月球返回的猎户座飞船阅读文章之前,请点击关注,方便您回来查看内容,以及参与大家的互动,感谢您给予我码字动力!工程师们已经开始对阿特米斯1号太空舱进行检查,以了解它在月球旅行和重返大气层时的状……
别老盯着人口规模红利了专家建议对50万以上存款征收利息税谈点个人看法:没有想到这样一条建议能够冲上头条热榜第一?背后还有什么政策逻辑,目前不得而知!我认为,这样的专家建议根本就不是什么好……
江西这两县若合并,可能成吉安新市区吉安是江西的一个地级市,很多人可能对这个地名不是很熟悉,但说到井冈山是不是就很熟悉了。著名的井冈山就坐落在吉安境内,除了井冈山,市内还有武功山、青原山等一系列风景名胜,吉州窑等……
云南省疾控中心提示春节期间儿童安全别放假!1月22日,云南省疾控中心发布最新提示,提醒大家,春节期间也要重视儿童安全问题,具体内容如下:云南疾控微信截图岁末年初,寒假和春节如期而至,同学们在休息调整、迎接新……
你和母亲的关系,就是你和世界的关系和母亲的关系是一个人命运的雏形有的人拼命想成为母亲的样子,有的人却拼命不想成为母亲的样子。有的人一生都在感谢母亲带给自己人生的礼物,而有的人却一生都清晰的记得母亲带给自己……
惊曝梅西泪别巴黎,冬窗转会纽卡斯尔,外媒太内涵了一条《突发!梅西泪别巴黎,即将加盟纽卡斯尔》的外文消息,今早成为热点。消息原文如下:在今天上午的发布会上,梅西强忍热泪宣布,他将离开巴黎圣日尔曼,转会前往纽卡斯尔。……
最亮的恒星最明亮的恒星是天狼星。在北半球的冬季,出现在岁末最后几天,午夜正南方向的,那颗最明亮的星星就是天狼星,天狼星A位于人犬座星座,那是大犬座a星,也叫大犬座主星。那质量是太阳……
山西,为什么如此重要?山西,为什么如此重要?有人说,山西不就有煤老板和暴发户吗这种看法真的是伤透了山西人的心事实上,很多人并不了解山西更不了解山西为何如此重要有人说,……
日本推出免税商品自动售货机方便旅日游客A。日语新闻中文版随着疫情的日渐结束,日本各大产业都在期待旅游业回暖、旅日外国游客增加拉动国内销售。在此情况下,日本国内的一些机场和车站出现了一种服务于外国游客的储物柜式……
中国两个城市明明很近,坐高铁却要12个小时,开车只需6个小时如今,交通设施越来越完善。人们出门时,可以通过各种交通方式满足自己的需求。一般来说,距离越近,时间越短。然而,中国有两个城市显然很近。坐高铁需要12个小时,开车只需要6个小时。……
51单片机学习记认识单片机与51单片机单片机与51单片机什么是单片机?单片机又称单片微处理器,简称mcu。单片机是一种集成电路,是将中央处理器cpu,随机存储器RAM,只读存储器ROM,和多种IO口,包含中断……