前言 spring事务失效场景可能大家在很多文章都看过了,所以今天就水一篇,看大家能不能收获一些不一样的东西。直接进入主题spring事务失效场景以及原因1、场景一:service没有托管给springpublicclassTranInvalidCaseWithoutInjectSpring{privateUserServiceuserSpublicTranInvalidCaseWithoutInjectSpring(UserServiceuserService){this。userServiceuserS}Transactionalpublicbooleanadd(Useruser){booleanisSuccessuserService。save(user);inti10;returnisS}}TestpublicvoidtestServiceWithoutInjectSpring(){booleanrandomBooleannewRandom()。nextBoolean();TranInvalidCaseWithoutInjectSpringtranInvalidCaseWithoutInjectSif(randomBoolean){tranInvalidCaseWithoutInjectSpringapplicationContext。getBean(TranInvalidCaseWithoutInjectSpring。class);System。out。println(service已经被spring托管);}else{tranInvalidCaseWithoutInjectSpringnewTranInvalidCaseWithoutInjectSpring(userService);System。out。println(service没被spring托管);}booleanisSuccesstranInvalidCaseWithoutInjectSpring。add(user);Assert。assertTrue(isSuccess);} 失效原因:spring事务生效的前提是,service必须是一个bean对象 解决方案:将service注入spring2、场景二:抛出受检异常ServicepublicclassTranInvalidCaseByThrowCheckException{AutowiredprivateUserServiceuserSTransactionalpublicbooleanadd(Useruser)throwsFileNotFoundException{booleanisSuccessuserService。save(user);newFileInputStream(1。txt);returnisS}}TestpublicvoidtestThrowCheckException()throwsException{booleanrandomBooleannewRandom()。nextBoolean();booleanisSTranInvalidCaseByThrowCheckExceptiontranInvalidCaseByThrowCheckExceptionapplicationContext。getBean(TranInvalidCaseByThrowCheckException。class);if(randomBoolean){System。out。println(配置Transactional(rollbackForException。class));isSuccesstranInvalidCaseByThrowCheckException。save(user);}else{System。out。println(配置Transactional);tranInvalidCaseByThrowCheckException。add(user);}Assert。assertTrue(isSuccess);} 失效原因:spring默认只会回滚非检查异常和error异常 解决方案:配置rollbackFor3、场景三:业务自己捕获了异常Transactionalpublicbooleanadd(Useruser){booleanisSuccessuserService。save(user);try{inti10;}catch(Exceptione){}returnisS}TestpublicvoidtestCatchExecption()throwsException{booleanrandomBooleannewRandom()。nextBoolean();booleanisSTranInvalidCaseWithCatchExceptiontranInvalidCaseByThrowCheckExceptionapplicationContext。getBean(TranInvalidCaseWithCatchException。class);if(randomBoolean){randomBooleannewRandom()。nextBoolean();if(randomBoolean){System。out。println(将异常原样抛出);tranInvalidCaseByThrowCheckException。save(user);}else{System。out。println(设置TransactionAspectSupport。currentTransactionStatus()。setRollbackOnly(););tranInvalidCaseByThrowCheckException。addWithRollBack(user);}}else{System。out。println(业务自己捕获了异常);tranInvalidCaseByThrowCheckException。add(user);}Assert。assertTrue(isSuccess);} 失效原因:spring事务只有捕捉到了业务抛出去的异常,才能进行后续的处理,如果业务自己捕获了异常,则事务无法感知 解决方案: 1、将异常原样抛出; 2、设置TransactionAspectSupport。currentTransactionStatus()。setRollbackOnly();4、场景四:切面顺序导致ServicepublicclassTranInvalidCaseWithAopSort{AutowiredprivateUserServiceuserSTransactionalpublicbooleansave(Useruser){booleanisSuccessuserService。save(user);try{inti10;}catch(Exceptione){thrownewRuntimeException();}returnisS}}AspectComponentSlf4jpublicclassAopAspect{Around(valueexecution(com。github。lybgeek。transcase。aopsort。。。(。。)))publicObjectaround(ProceedingJoinPointpjp){try{System。out。println(这是一个切面);returnpjp。proceed();}catch(Throwablethrowable){log。error({},throwable);}}} 失效原因:spring事务切面的优先级顺序最低,但如果自定义的切面优先级和他一样,且自定义的切面没有正确处理异常,则会同业务自己捕获异常的那种场景一样 解决方案: 1、在切面中将异常原样抛出; 2、在切面中设置TransactionAspectSupport。currentTransactionStatus()。setRollbackOnly();5、场景五:非public方法ServicepublicclassTranInvalidCaseWithAccessPerm{AutowiredprivateUserServiceuserSTransactionalprotectedbooleansave(Useruser){booleanisSuccessuserService。save(user);try{inti10;}catch(Exceptione){thrownewRuntimeException();}returnisS}}publicclassTranInvalidCaseWithAccessPermTest{publicstaticvoidmain(String〔〕args){ConfigurableApplicationContextcontextSpringApplication。run(Application。class);TranInvalidCaseWithAccessPermtranInvalidCaseWithAccessPermcontext。getBean(TranInvalidCaseWithAccessPerm。class);booleanisSuccesstranInvalidCaseWithAccessPerm。save(UserUtils。getUser());System。out。println(isSuccess);}} 失效原因:spring事务默认生效的方法权限都必须为public 解决方案: 1、将方法改为 2、修改TansactionAttributeSource,将publicMethodsOnly改为false【这个从源码跟踪得出结论】 3、开启AspectJ代理模式【从spring文档得出结论】文档如下 MethodvisibilityandTransactional Whenusingproxies,youshouldapplytheTransactionalannotationonlytomethodswithpublicvisibility。Ifyoudoannotateprotected,privateorpackagevisiblemethodswiththeTransactionalannotation,noerrorisraised,buttheannotatedmethoddoesnotexhibittheconfiguredtransactionalsettings。ConsidertheuseofAspectJ(seebelow)ifyouneedtoannotatenonpublicmethods。 具体步骤: 1、在pom引入aspectjrt坐标以及相应插件dependencygroupIdorg。aspectjgroupIdaspectjrtartifactIdversion1。8。9versiondependencyplugingroupIdorg。codehaus。mojogroupIdaspectjmavenpluginartifactIdversion1。9versionconfigurationshowWeaveInfotrueshowWeaveInfogroupIdorg。springframeworkgroupIdspringaspectsartifactIdaspectLibraryaspectLibrariesconfigurationexecutionsexecutiongoalsgoalcompilegoal!usethisgoaltoweaveallyourmainclassesgoaltestcompilegoal!usethisgoaltoweaveallyourtestclassesgoalsexecutionexecutionsplugin 2、在启动类上加上如下配置EnableTransactionManagement(modeAdviceMode。ASPECTJ) 注:如果是在idea上运行,则需做如下配置 4、直接用TransactionTemplate 示例:AutowiredprivateTransactionTemplatetransactionTprivatevoidprocess(){transactionTemplate。execute(newTransactionCallbackWithoutResult(){OverrideprotectedvoiddoInTransactionWithoutResult(TransactionStatusstatus){processInTransaction();}});}6、场景六:父子容器 失效原因:子容器扫描范围过大,将未加事务配置的serivce扫描进来 解决方案: 1、父子容器个扫个的范围; 2、不用父子容器,所有bean都交给同一容器管理 注:因为示例是使用springboot,而springboot启动默认没有父子容器,只有一个容器,因此就该场景就演示示例了7、场景七:方法用final修饰Transactionalpublicfinalbooleanadd(Useruser,UserServiceuserService){booleanisSuccessuserService。save(user);try{inti10;}catch(Exceptione){thrownewRuntimeException();}returnisS} 失效原因:因为spring事务是用动态代理实现,因此如果方法使用了final修饰,则代理类无法对目标方法进行重写,植入事务功能 解决方案: 1、方法不要用final修饰8、场景八:方法用static修饰Transactionalpublicstaticbooleansave(Useruser,UserServiceuserService){booleanisSuccessuserService。save(user);try{inti10;}catch(Exceptione){thrownewRuntimeException();}returnisS} 失效原因:原因和final一样 解决方案: 1、方法不要用static修饰9、场景九:调用本类方法publicbooleansave(Useruser){returnthis。saveUser(user);}TransactionalpublicbooleansaveUser(Useruser){booleanisSuccessuserService。save(user);try{inti10;}catch(Exceptione){thrownewRuntimeException();}returnisS} 失效原因:本类方法不经过代理,无法进行增强 解决方案: 1、注入自己来调用; 2、使用EnableAspectJAutoProxy(exposeProxytrue)AopContext。currentProxy()10、场景十:多线程调用Transactional(rollbackForException。class)publicbooleansave(Useruser)throwsExecutionException,InterruptedException{FutureBooleanfutureexecutorService。submit((){booleanisSuccessuserService。save(user);try{inti10;}catch(Exceptione){thrownewException();}returnisS});returnfuture。get();} 失效原因:因为spring的事务是通过数据库连接来实现,而数据库连接spring是放在threadLocal里面。同一个事务,只能用同一个数据库连接。而多线程场景下,拿到的数据库连接是不一样的,即是属于不同事务11、场景十一:错误的传播行为Transactional(propagationPropagation。NOTSUPPORTED)publicbooleansave(Useruser){booleanisSuccessuserService。save(user);try{inti10;}catch(Exceptione){thrownewRuntimeException();}returnisS} 失效原因:使用的传播特性不支持事务12、场景十二:使用了不支持事务的存储引擎 失效原因:使用了不支持事务的存储引擎。比如mysql中的MyISAM13、场景十三:数据源没有配置事务管理器 注:因为springboot,他默认已经开启事务管理器。org。springframework。boot。autoconfigure。jdbc。DataSourceTransactionManagerAutoConfiguration。因此示例略过14、场景十四:被代理的类过早实例化ServicepublicclassTranInvalidCaseInstantiatedTooEarlyimplementsBeanPostProcessor,Ordered{AutowiredprivateUserServiceuserSTransactionalpublicbooleansave(Useruser){booleanisSuccessuserService。save(user);try{inti10;}catch(Exceptione){thrownewRuntimeException();}returnisS}OverridepublicintgetOrder(){return1;}} 失效原因:当代理类的实例化早于AbstractAutoProxyCreator后置处理器,就无法被AbstractAutoProxyCreator后置处理器增强总结 本文列举了14种spring事务失效的场景,其实这14种里面有很多都是归根结底都是属于同一类问题引起,比如因为动态代理原因、方法限定符原因、异常类型原因等demo链接 https:github。comlybgeekspringbootlearningtreemasterspringboottransactioninvalidcase