Spring transaction error: org.springframework.transaction.UnexpectedRollbackException

Exception information: An unpredictable rollback exception occurred. Because the transaction has been flagged and can only be rolled back, the transaction was rolled back.

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)

The Spring transaction propagation mechanism is summarized as follows:

  1. PROPAGATION_REQUIRED: If there is no current transaction, create a new transaction. If there is already a transaction, add it to this transaction. Default policy
  2. PROPAGATION_SUPPORTS: Supports the current transaction. If there is no current transaction, it will be executed in a non-transactional manner.
  3. PROPAGATION_MANDATORY: Use the current transaction. If there is no current transaction, throw an exception.
  4. PROPAGATION_REQUIRES_NEW: Create a new transaction. If a transaction currently exists, suspend the current transaction.
  5. PROPAGATION_NOT_SUPPORTED: Perform the operation in a non-transactional manner. If a transaction currently exists, the current transaction will be suspended.
  6. PROPAGATION_NEVER: Execute in a non-transactional manner and throw an exception if a transaction currently exists. \
  7. PROPAGATION_NESTED: If a transaction currently exists, execute within a nested transaction. If there is no current transaction, perform similar operations to PROPAGATION_REQUIRED.
    The sub-exception of nested is thrown if the external insertion is successful when it is caught.
    The sub-exception of nested is thrown. If it is not caught, external insertion fails.
    An exception is thrown outside nested and the insertion inside nest is successful. Nest will also roll back accordingly.

Spring transaction default propagation behavior:

@Transactional is equal to @Transactional(propagation=Propagation.REQUIRED)

Description of the abnormal scenario:

When using Spring transactions, a transaction B is opened in a transaction A (that is, there is a nested transaction). When an exception occurs in transaction B, transaction B will perform a rollback operation after catching the exception. If the exception is directly Eaten (that is, transaction A cannot know that an exception has occurred), transaction A will throw the above exception.

serviceA has transaction @Transactional
serviceB has transaction @Transactional

 serviceA.methodA()
 {<!-- -->
       doSomethingA();
      try {<!-- -->
             serviceB.methodB{<!-- -->}; //There are exceptions marked as rollback, doSetRollbackOnly(status);
          }
           catch {<!-- -->
                //When the captured exception is transferred to commit, since it has been marked for rollback, rollback and throw a new exception
               }
       doSomethingB();
  }

reason:
Because the propagation attribute of methodB is set to PROPAGATION_REQUIRED, PROPAGATION_REQUIRED means that if there is currently a transaction, the current transaction will be used, and if there is no current transaction, a transaction will be created. Since the propagation attribute of methodA is also PROPAGATION_REQUIRED, methodA will create a transaction, and then methodB and methodA will use the same transaction. After an exception occurs in methodB, the current transaction flag will be rolled back. Since trycatch processing is done in methodA, the program will not terminate. Instead, we continue to go down. When the transaction commits, we check the status and find that the transaction needs to be rolled back, so an unpredictable transaction exception occurs: because the transaction is flagged for rollback, the transaction is rolled back.
That is to say: methodA and methodB share a transaction, methodB marks the transaction as rollback, methodA commits the transaction, and then, an exception message appears that the transaction has been marked for rollback (marked by methodB).

solution

Case 1: methodA and methodB should not logically belong to the same transaction, then modify the transaction propagation attribute of methodB to PROPAGATION_REQUIRES_NEW. In this way, when methodB is executed, a new transaction will be created without affecting the transaction in methodA.

Situation 2: Business A and business B should belong to the same transaction in terms of business logic, then remove the try catch in methodA

Situation 3: Business A and business B should belong to the same transaction in terms of business logic, but the failure of methodB cannot affect the transaction submission of methodA, so still try to catch methodB in methodA, and set methodB to PROPAGATION_NESTED. Its This means that methodB is a sub-transaction and has a savepoint. If it fails, it will be rolled back to the savepoint without affecting methodA. If it succeeds, A and B will be submitted together. A and B are both a transaction, but B is a sub-transaction.

Scenario 4: There is no impact whether business A has a transaction or not. Remove @Transactional of business A.

summary:

Deeply understand the meaning of each status of transaction propagation, such as PROPAGATION_REQUIRED, PROPAGATION_SUPPORTS, PROPAGATION_MANDATORY, PROPAGATION_REQUIRES_NEW, etc.

At first, I thought it was because the transaction of methodA was set to ready-only=true (read-only transaction) in the spring configuration file. After querying the data, I learned that read-only transactions and transaction propagation do not conflict and are two mechanisms. A read-only transaction is a special transaction. Within this transaction, there can only be query statements and cannot contain modification or update statements. The database may optimize read-only transactions; the propagation attribute is the expression of the relationship between the parent method and the child method.

Note that in the same class, transaction nesting is based on the outermost method, and nested transactions will be invalid; only transactions nested in different classes will take effect;