Spring事務報錯: org.springframework.transaction.UnexpectedRollbackException

異常信息: 出現了不可預知的回滾異常,因爲事務已經被標誌位只能回滾,所以事務回滾了。

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:724)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:485)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:291)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)
	at com.sun.proxy.$Proxy98.evaluateScript(Unknown Source)
	at com.mdiaf.baf.action.BafConsoleController.eval(BafConsoleController.java:32)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:606)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:222)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:814)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:737)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:969)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:871)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:647)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:845)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
	at com.mdiaf.webapp.filter.BafPostAuthFilter.continueFilter(BafPostAuthFilter.java:76)
	at com.mdiaf.webapp.filter.BafPostAuthFilter.doFilterInternal(BafPostAuthFilter.java:58)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:316)
	at org.springframework.security.web.authentication.switchuser.SwitchUserFilter.doFilter(SwitchUserFilter.java:193)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:126)
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
	at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:114)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
	at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:122)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
	at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
	at org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter.doFilter(RememberMeAuthenticationFilter.java:157)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
	at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:169)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
	at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:48)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
	at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:205)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
	at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:120)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
	at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
	at com.mdiaf.webapp.filter.BafPreAuthFilter.doFilterInternal2(BafPreAuthFilter.java:119)
	at com.mdiaf.webapp.filter.BafPreAuthFilter.doFilterInternal(BafPreAuthFilter.java:70)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
	at org.springframework.web.multipart.support.MultipartFilter.doFilterInternal(MultipartFilter.java:118)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
	at com.mdiaf.webapp.filter.GZIPFilter.doFilter(GZIPFilter.java:28)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
	at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
	at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041)
	at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
	at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
	at java.lang.Thread.run(Thread.java:745)

Spring事務傳播機制彙總如下:

  1. PROPAGATION_REQUIRED:如果當前沒有事務,就新建一個事務,如果已經存在一個事務,就加入到這個事務中。默認策略
  2. PROPAGATION_SUPPORTS:支持當前事務,如果當前沒有事務,就以非事務方式執行。
  3. PROPAGATION_MANDATORY:使用當前的事務,如果當前沒有事務,就拋出異常。
  4. PROPAGATION_REQUIRES_NEW:新建事務,如果當前存在事務,把當前事務掛起。
  5. PROPAGATION_NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
  6. PROPAGATION_NEVER:以非事務方式執行,如果當前存在事務,則拋出異常。
  7. PROPAGATION_NESTED:如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。
    nested 的子異常拋出 如果被catch 外部插入成功
    nested 的子異常拋出 如果不被catch 外部插入失敗
    nested外面異常拋出 nest內部插入成功 nest也會跟着回滾

spring事務默認的傳播行爲:
@Transactional 等於 @Transactional(propagation=Propagation.REQUIRED)

發生異常的場景描述:
在使用Spring事務時,在一個事務A中又開了一個事務B(即存在嵌套事務),當事務B發生異常時,將異常catch後事務B會進行回滾操作,若此時異常被直接喫掉(即事務A無法得知發生過異常),則事務A會拋出如上異常。

 serviceA有事務 @Transactional
 serviceB有事務 @Transactional
 
  serviceA.methodA() 
 {  
       doSomethingA();  
      try {  
             serviceB.methodB{}; //這裏面有異常標記爲回滾, doSetRollbackOnly(status);  
          }
           catch {  
                //捕獲異常轉到commit時,由於已經標記爲要回滾, 回滾並拋出新異常     
               }         
       doSomethingB();  
  }   

原因:
因爲methodB的傳播屬性設置爲PROPAGATION_REQUIRED,PROPAGATION_REQUIRED的意思是,當前有事務,則使用當前事務,當前無事務則創建事務。由於methodA的傳播屬性也爲PROPAGATION_REQUIRED,所以methodA會創建一個事務,然後methodB與methodA使用同一個事務,methodB出現異常後,將當前事務標誌位回滾,由於在methodA中做了trycatch處理,程序沒有終止而是繼續往下走,當事務commit時,check狀態,發現,需要事務回滾,所以纔會出現不可預知的事務異常:因爲事務被標誌位回滾,所以事務回滾。
也就是說:methodA與methodB共用一個事務,methodB將事務標誌爲回滾,methodA中commit這個事務,然後,出現事務已經被標誌回滾(methodB標誌的)的異常信息。

解決方案
情況1:methodA與methodB在邏輯上不應該屬於同一個事務,那麼將methodB的事務傳播屬性修改爲PROPAGATION_REQUIRES_NEW,這樣,執行methodB時,會創建一個新的事務,不影響methodA中的事務。

情況2:業務A與業務B在業務邏輯上就應該屬於同一個事務,那麼將methodA中的try catch去掉

情況3:業務A與業務B在業務邏輯上就應該屬於同一個事務,但是methodB的失敗與否不能影響methodA的事務提交,那麼仍然在methodA中try catch methodB,並將methodB設置爲PROPAGATION_NESTED,它的意思是,methodB是一個子事務,有一個savepoint,失敗時會回滾到savepoint,不影響methodA,如果成功則A、B一起提交,A與B都是一個事務,只是B是一個子事務。
情況4:業務A 有無事務無影響 去掉業務A的@Transactional

小結:

  1. 深入理解事務傳播的各個狀態含義,比如PROPAGATION_REQUIRED、PROPAGATION_SUPPORTS、PROPAGATION_MANDATORY、PROPAGATION_REQUIRES_NEW等
  2. 最初我以爲是由於spring配置文件中,methodA的事務設置爲ready-only=true(只讀事務)的原因,經過查詢資料得知,只讀事務與事務傳播不衝突,是兩個機制。只讀事務,是一個特殊的事務,該事務內,只能是查詢語句,不能含有修改、更新語句,數據庫可能對只讀事務做優化;傳播屬性是母方法與子方法之間的關係表達。
  3. 注意,同一個類中,事務嵌套以最外層的方法爲準,嵌套的事務失效;不同類中嵌套的事務纔會生效;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章