解决org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back beca

2023-11-11

1. 复现错误


今天本地使用Knife4j调用后端接口时,报出如下图的错误:

在这里插入图片描述

于是,查看后端控制台的输出结果,如下所示:

org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:752)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:654)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:407)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)
	at com.test.cloud.service.ModelService$$EnhancerBySpringCGLIB$$a05f18b.generatePage(<generated>)
	at com.test.cloud.controller.ModelController.generatePage(ModelController.java:169)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1063)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:652)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	......

Transaction silently rolled back because it has been marked as rollback-only

之前也遇到这个错误,但由于不太懂事务的嵌套,且总不能重现这个错误。

趁着此次的错误,彻底研究该错误形成的原因。

2. 分析错误


Transaction silently rolled back because it has been marked as rollback-only翻译成中文,即事务以静默方式回滚,因为它已被标记为仅回滚

那么,这句话是什么意思呢?通过对以下代码的分析,来了解它的含义。

因为公司代码不能泄露,特写如下三个测试类:

  1. CommonApplicationTestsspring boot自带的测试类
@SpringBootTest
class CommonApplicationTests {

  @Autowired
  TestRollbackService testRollbackService;

  public void testRollback(){
    try{
      testRollbackService.functionOne();
    }catch(Exception e){
      e.printStackTrace();
    }
  }
}
  1. TestRollbackService:事务回滚类
@Autowired
private UserMapper userMapper;

@Autowired
private TestTransactionService testTransactionService;

@Transactional(rollbackFor = BizException.class)
public void functionOne(){
	try{
      	User updateUser = new User();
    	updateUser.setId(new Long(19));
    	updateUser.setName("super先生");
    	userMapper.updateByPrimaryKey(updateUser);
        testTransactionService.functionTwo();
    }catch(Exception e){
        e.printStackTrace();
    }
}
  1. TestTransactionService:事务执行类
@Autowired
private AddressMapper addressMapper;
 
@Transactional(rollbackFor = BizException.class)
public void functionTwo(){
  Address addressUpdate=new Address();
  addressUpdate.setId(1L);
  addressUpdate.setDetail("无锡市经开区")
  addressMapper.updateByPrimaryKey(addressUpdate);
  System.out.println(1/0);
}

如上两个方法,分别为functionOnefunctionTwo

functionOne加上了 @Transactional(rollbackFor = BizException.class)

与此同时,functionTwo也加上了 @Transactional(rollbackFor = BizException.class)

你在functionOne中调用了functionTwo,而functionTwo报出了错误信息,即可触发回滚异常的报错。

两个方法都加了事务注解,它们都会受到到事务管理的拦截器增强,并且事务传播的方式都是默认的,也就是REQUIRED,当已经存在事务的时候就加入事务,没有就创建事务。

这里functionOnefunctionTwo都受事务控制,并且是处于同一个事务的。

functionOne调用了functionTwofunctionOne中抓了functionTwo的异常。当functionTwo发生异常时,functionTwo的操作应该回滚。

functionOne吃了异常,functionOne方法中没有产生异常,所以functionOne的操作又应该提交,二者是相互矛盾的。

spring的事务关联拦截器在抓到functionTwo的异常后,就会标记rollback-only为true。当functionOne执行完准备提交后,发现rollback-only为true,也会回滚,并抛出异常告诉调用者。

程序时序图如下:

在这里插入图片描述

3. 分析spring的事务机制

3.1 入口程序


程序入口走cglib的代理类(CglibAopProxy)。

找到CglibAopProxyDynamicAdvisedInterceptor.class内部类,可以看到入口方法(intercept)如下:

@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    Object target = null;
    TargetSource targetSource = this.advised.getTargetSource();

    Object var16;
    .....
}

在该方法(intercept)中的找到如下语句:

在这里插入图片描述

即语句retVal = (new CglibAopProxy.CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)).proceed();

点击上图中CglibMethodInvocation类,即可到CglibAopProxyCglibMethodInvocation内部类的如下方法:

在这里插入图片描述

然后找到TransactionInterceptor类,即到了事务管理的拦截器,如下图所示:

在这里插入图片描述

3.2 事务管理的主方法


invokeWithinTransaction是事务管理的主方法。

@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, TransactionAspectSupport.InvocationCallback invocation) throws Throwable {
    TransactionAttributeSource tas = this.getTransactionAttributeSource();
    TransactionAttribute txAttr = tas != null ? tas.getTransactionAttribute(method, targetClass) : null;
    TransactionManager tm = this.determineTransactionManager(txAttr);
    if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {
        boolean isSuspendingFunction = KotlinDetector.isSuspendingFunction(method);
        boolean hasSuspendingFlowReturnType = isSuspendingFunction && "kotlinx.coroutines.flow.Flow".equals((new MethodParameter(method, -1)).getParameterType().getName());
        if (isSuspendingFunction && !(invocation instanceof TransactionAspectSupport.CoroutinesInvocationCallback)) {
            throw new IllegalStateException("Coroutines invocation not supported: " + method);
        } else {
            TransactionAspectSupport.CoroutinesInvocationCallback corInv = isSuspendingFunction ? (TransactionAspectSupport.CoroutinesInvocationCallback)invocation : null;
            TransactionAspectSupport.ReactiveTransactionSupport txSupport = (TransactionAspectSupport.ReactiveTransactionSupport)this.transactionSupportCache.computeIfAbsent(method, (key) -> {
                Class<?> reactiveType = isSuspendingFunction ? (hasSuspendingFlowReturnType ? Flux.class : Mono.class) : method.getReturnType();
                ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(reactiveType);
                if (adapter == null) {
                    throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " + method.getReturnType());
                } else {
                    return new TransactionAspectSupport.ReactiveTransactionSupport(adapter);
                }
            });
            TransactionAspectSupport.InvocationCallback callback = invocation;
            if (corInv != null) {
                callback = () -> {
                    return CoroutinesUtils.invokeSuspendingFunction(method, corInv.getTarget(), corInv.getArguments());
                };
            }

            Object result = txSupport.invokeWithinTransaction(method, targetClass, callback, txAttr, (ReactiveTransactionManager)tm);
            if (corInv != null) {
                Publisher<?> pr = (Publisher)result;
                return hasSuspendingFlowReturnType ? TransactionAspectSupport.KotlinDelegate.asFlow(pr) : TransactionAspectSupport.KotlinDelegate.awaitSingleOrNull(pr, corInv.getContinuation());
            } else {
                return result;
            }
        }
    } else {
        PlatformTransactionManager ptm = this.asPlatformTransactionManager(tm);
        String joinpointIdentification = this.methodIdentification(method, targetClass, txAttr);
        if (txAttr != null && ptm instanceof CallbackPreferringPlatformTransactionManager) {
            TransactionAspectSupport.ThrowableHolder throwableHolder = new TransactionAspectSupport.ThrowableHolder();

            Object result;
            try {
                result = ((CallbackPreferringPlatformTransactionManager)ptm).execute(txAttr, (statusx) -> {
                    TransactionAspectSupport.TransactionInfo txInfo = this.prepareTransactionInfo(ptm, txAttr, joinpointIdentification, statusx);

                    Object var9;
                    try {
                        Object retVal = invocation.proceedWithInvocation();
                        if (retVal != null && vavrPresent && TransactionAspectSupport.VavrDelegate.isVavrTry(retVal)) {
                            retVal = TransactionAspectSupport.VavrDelegate.evaluateTryFailure(retVal, txAttr, statusx);
                        }

                        var9 = retVal;
                        return var9;
                    } catch (Throwable var13) {
                        if (txAttr.rollbackOn(var13)) {
                            if (var13 instanceof RuntimeException) {
                                throw (RuntimeException)var13;
                            }

                            throw new TransactionAspectSupport.ThrowableHolderException(var13);
                        }

                        throwableHolder.throwable = var13;
                        var9 = null;
                    } finally {
                        this.cleanupTransactionInfo(txInfo);
                    }

                    return var9;
                });
            } catch (TransactionAspectSupport.ThrowableHolderException var22) {
                throw var22.getCause();
            } catch (TransactionSystemException var23) {
                if (throwableHolder.throwable != null) {
                    this.logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
                    var23.initApplicationException(throwableHolder.throwable);
                }

                throw var23;
            } catch (Throwable var24) {
                if (throwableHolder.throwable != null) {
                    this.logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
                }

                throw var24;
            }

            if (throwableHolder.throwable != null) {
                throw throwableHolder.throwable;
            } else {
                return result;
            }
        } else {
            TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

            Object retVal;
            try {
                retVal = invocation.proceedWithInvocation();
            } catch (Throwable var20) {
                this.completeTransactionAfterThrowing(txInfo, var20);
                throw var20;
            } finally {
                this.cleanupTransactionInfo(txInfo);
            }

            if (retVal != null && vavrPresent && TransactionAspectSupport.VavrDelegate.isVavrTry(retVal)) {
                TransactionStatus status = txInfo.getTransactionStatus();
                if (status != null && txAttr != null) {
                    retVal = TransactionAspectSupport.VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
                }
            }

            this.commitTransactionAfterReturning(txInfo);
            return retVal;
        }
    }
}

程序执行的是else分支,步骤很清晰

  1. 获取TransactionAttribute

  2. 基于TransactionAttribute获取TransactionManager

  3. 基于TransactionAttribute获取joinpointIdentification(没研究什么作用)

  4. 基于1,2,3创建的对象获取 TransactionAspectSupport.TransactionInfotransactionInfoTransactionAspectSupport的一个内部类

  5. 执行业务方法

  6. 抓到异常就回滚,并清除事务,然后向上抛异常;没有异常就清除事务,然后提交。

对象之间的关联关系如下图所示:

在这里插入图片描述

3.3 细究各对象的获取


TransactionManager的获取比较简单,程序里获取到的其实就是自己配置的bean

创建TransactionInfo的过程中要先获取TransactionStatus

TransactionStatus又需要拿到ConnectionHolder

  1. createTransactionIfNecessary

createTransactionIfNecessary方法在类TransactionAspectSupport中。

protected TransactionAspectSupport.TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
    if (txAttr != null && ((TransactionAttribute)txAttr).getName() == null) {
        txAttr = new DelegatingTransactionAttribute((TransactionAttribute)txAttr) {
            public String getName() {
                return joinpointIdentification;
            }
        };
    }

    TransactionStatus status = null;
    if (txAttr != null) {
        if (tm != null) {
            status = tm.getTransaction((TransactionDefinition)txAttr);
        } else if (this.logger.isDebugEnabled()) {
            this.logger.debug("Skipping transactional joinpoint [" + joinpointIdentification + "] because no transaction manager has been configured");
        }
    }

    return this.prepareTransactionInfo(tm, (TransactionAttribute)txAttr, joinpointIdentification, status);
}
  1. 获取TransactionStatus

这里通过status = tm.getTransaction((TransactionDefinition)txAttr)语句来创建TransactionStatus

接着看语句中的getTransaction方法,它在在类AbstractPlatformTransactionManager中,如下所示:

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
    Object transaction = this.doGetTransaction();
    boolean debugEnabled = this.logger.isDebugEnabled();
    if (definition == null) {
        definition = new DefaultTransactionDefinition();
    }

    if (this.isExistingTransaction(transaction)) {
        return this.handleExistingTransaction((TransactionDefinition)definition, transaction, debugEnabled);
    } else if (((TransactionDefinition)definition).getTimeout() < -1) {
        throw new InvalidTimeoutException("Invalid transaction timeout", ((TransactionDefinition)definition).getTimeout());
    } else if (((TransactionDefinition)definition).getPropagationBehavior() == 2) {
        throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation 'mandatory'");
    } else if (((TransactionDefinition)definition).getPropagationBehavior() != 0 && ((TransactionDefinition)definition).getPropagationBehavior() != 3 && ((TransactionDefinition)definition).getPropagationBehavior() != 6) {
        if (((TransactionDefinition)definition).getIsolationLevel() != -1 && this.logger.isWarnEnabled()) {
            this.logger.warn("Custom isolation level specified but no actual transaction initiated; isolation level will effectively be ignored: " + definition);
        }

        boolean newSynchronization = this.getTransactionSynchronization() == 0;
        return this.prepareTransactionStatus((TransactionDefinition)definition, (Object)null, true, newSynchronization, debugEnabled, (Object)null);
    } else {
        AbstractPlatformTransactionManager.SuspendedResourcesHolder suspendedResources = this.suspend((Object)null);
        if (debugEnabled) {
            this.logger.debug("Creating new transaction with name [" + ((TransactionDefinition)definition).getName() + "]: " + definition);
        }

        try {
            boolean newSynchronization = this.getTransactionSynchronization() != 2;
            DefaultTransactionStatus status = this.newTransactionStatus((TransactionDefinition)definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
            this.doBegin(transaction, (TransactionDefinition)definition);
            this.prepareSynchronization(status, (TransactionDefinition)definition);
            return status;
        } catch (Error | RuntimeException var7) {
            this.resume((Object)null, suspendedResources);
            throw var7;
        }
    }
}
  1. 获取transactionStatus前先获取DataSourceTransactionObject

通过程序最上面的语句 Object transaction = this.doGetTransaction();来创建DataSourceTransactionObject对象,这是DataSourceTransactionManager的内部类

我们来看语句中的doGetTransaction方法,它在类DataSourceTransactionManager中,如下代码所示:

protected Object doGetTransaction() {
   DataSourceTransactionManager.DataSourceTransactionObject txObject = new DataSourceTransactionManager.DataSourceTransactionObject();
   txObject.setSavepointAllowed(this.isNestedTransactionAllowed());
   ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(this.obtainDataSource());
   txObject.setConnectionHolder(conHolder, false);
   return txObject;
}

同时,这里获取了ConnectionHolder对象,此处的newConnectionHolderfalse

接着看doGetTransaction方法中的TransactionSynchronizationManager.getResource方法,如下代码所示:

@Nullable
public static Object getResource(Object key) {
    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
    Object value = doGetResource(actualKey);
    if (value != null && logger.isTraceEnabled()) {
        logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
    }
    return value;
}

通过一个key获取的,类似于从一个池子里面拿东西一样。

其实当functionOne方法执行时,并没有获取到ConnectionHolder,拿到的是null

我们继续看getResource方法中的doGetResource方法。

doGetResource方法是在类TransactionSynchronizationManager中。

private static Object doGetResource(Object actualKey) {
    Map<Object, Object> map = (Map)resources.get();
    if (map == null) {
        return null;
    } else {
        Object value = map.get(actualKey);
        if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {
            map.remove(actualKey);
            if (map.isEmpty()) {
                resources.remove();
            }
 
            value = null;
        }
 
        return value;
    }
}

resources对象其实是一个ThreadLocal,意思是同一个线程中拿到的ConnectionHolder是相同的。

  1. doBegin方法

doBegin方法是在类DataSourceTransactionManager中。

protected void doBegin(Object transaction, TransactionDefinition definition) {
    DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)transaction;
    Connection con = null;
 
    try {
        if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
            Connection newCon = this.obtainDataSource().getConnection();
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
            }
 
            txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
        }
 
        txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
        con = txObject.getConnectionHolder().getConnection();
        Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
        txObject.setPreviousIsolationLevel(previousIsolationLevel);
        if (con.getAutoCommit()) {
            txObject.setMustRestoreAutoCommit(true);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
            }
 
            con.setAutoCommit(false);
        }
 
        this.prepareTransactionalConnection(con, definition);
        txObject.getConnectionHolder().setTransactionActive(true);
        int timeout = this.determineTimeout(definition);
        if (timeout != -1) {
            txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
        }
 
        if (txObject.isNewConnectionHolder()) {
            TransactionSynchronizationManager.bindResource(this.obtainDataSource(), txObject.getConnectionHolder());
        }
 
    } catch (Throwable var7) {
        if (txObject.isNewConnectionHolder()) {
            DataSourceUtils.releaseConnection(con, this.obtainDataSource());
            txObject.setConnectionHolder((ConnectionHolder)null, false);
        }
 
        throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", var7);
    }
}

截取该方法的重要几行:

Connection newCon = this.obtainDataSource().getConnection();
if (this.logger.isDebugEnabled()) {
    this.logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
 
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);

先获取连接(java.sql.Connection),接着创建ConnectionHolder

newConnectionHolder设置为true,如果之前不为空,newConnectionHolder则为false

如果newConnectionHoldertrue,还需要将connectionHolder放到threadLocal里面,让后面的方法可以获取到相同的ConnectionHolder,截取的代码如下:


if (txObject.isNewConnectionHolder()) {
    TransactionSynchronizationManager.bindResource(this.obtainDataSource(), txObject.getConnectionHolder());
}

到此,TransactionStatus就创建好了。

  1. 获取TransactionInfo

首先看类TransactionAspectSupport中的prepareTransactionInfo

protected TransactionAspectSupport.TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr, String joinpointIdentification, @Nullable TransactionStatus status) {
    TransactionAspectSupport.TransactionInfo txInfo = new TransactionAspectSupport.TransactionInfo(tm, txAttr, joinpointIdentification);
    if (txAttr != null) {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
        }
 
        txInfo.newTransactionStatus(status);
    } else if (this.logger.isTraceEnabled()) {
        this.logger.trace("Don't need to create transaction for [" + joinpointIdentification + "]: This method isn't transactional.");
    }
 
    txInfo.bindToThread();
    return txInfo;
}

我们细看txInfo.bindToThread();方法。

bindToThread方法在类TransactionAspectSupport.TransactionInfo中,如下代码所示:

private void bindToThread() {
    this.oldTransactionInfo = (TransactionAspectSupport.TransactionInfo)TransactionAspectSupport.transactionInfoHolder.get();
    TransactionAspectSupport.transactionInfoHolder.set(this);
}
java.lang.ThreadLocal#get 

java.lang.ThreadLocal类中的get方法如下:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

bindToThread()的的作用是获取oldTransactionInfo,还有线程有有关。

4. functionTwo方法抛异常后的回滚操作


我们首先看类TransactionAspectSupport中的completeTransactionAfterThrowing方法,如下所示:

protected void completeTransactionAfterThrowing(@Nullable TransactionAspectSupport.TransactionInfo txInfo, Throwable ex) {
    if (txInfo != null && txInfo.getTransactionStatus() != null) {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "] after exception: " + ex);
        }
 
        if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
            try {
                txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
            } catch (TransactionSystemException var6) {
                this.logger.error("Application exception overridden by rollback exception", ex);
                var6.initApplicationException(ex);
                throw var6;
            } catch (Error | RuntimeException var7) {
                this.logger.error("Application exception overridden by rollback exception", ex);
                throw var7;
            }
        } else {
            try {
                txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
            } catch (TransactionSystemException var4) {
                this.logger.error("Application exception overridden by commit exception", ex);
                var4.initApplicationException(ex);
                throw var4;
            } catch (Error | RuntimeException var5) {
                this.logger.error("Application exception overridden by commit exception", ex);
                throw var5;
            }
        }
    }
}

txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());调用transactionManager进行rollback

接着看AbstractPlatformTransactionManager类中的rollback方法,如下所示:

public final void rollback(TransactionStatus status) throws TransactionException {
    if (status.isCompleted()) {
        throw new IllegalTransactionStateException("Transaction is already completed - do not call commit or rollback more than once per transaction");
    } else {
        DefaultTransactionStatus defStatus = (DefaultTransactionStatus)status;
        this.processRollback(defStatus, false);
    }
}

进一步调自身的processRollback,那就继续看类中AbstractPlatformTransactionManagerprocessRollback方法,如下所示:

private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
    try {
        boolean unexpectedRollback = unexpected;
 
        try {
            this.triggerBeforeCompletion(status);
            if (status.hasSavepoint()) {
                if (status.isDebug()) {
                    this.logger.debug("Rolling back transaction to savepoint");
                }
 
                status.rollbackToHeldSavepoint();
            } else if (status.isNewTransaction()) {
                if (status.isDebug()) {
                    this.logger.debug("Initiating transaction rollback");
                }
 
                this.doRollback(status);
            } else {
                if (status.hasTransaction()) {
                    if (!status.isLocalRollbackOnly() && !this.isGlobalRollbackOnParticipationFailure()) {
                        if (status.isDebug()) {
                            this.logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
                        }
                    } else {
                        if (status.isDebug()) {
                            this.logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
                        }
 
                        this.doSetRollbackOnly(status);
                    }
                } else {
                    this.logger.debug("Should roll back transaction but cannot - no transaction available");
                }
 
                if (!this.isFailEarlyOnGlobalRollbackOnly()) {
                    unexpectedRollback = false;
                }
            }
        } catch (Error | RuntimeException var8) {
            this.triggerAfterCompletion(status, 2);
            throw var8;
        }
 
        this.triggerAfterCompletion(status, 1);
        if (unexpectedRollback) {
            throw new UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only");
        }
    } finally {
        this.cleanupAfterCompletion(status);
    }
 
}

this.triggerBeforeCompletion(status) 方法好像释放了连接。

functionTwo不是新事务,所以最后会执行this.doSetRollbackOnly(status)

进而看类DataSourceTransactionManager中的doSetRollbackOnly方法,如下所示:

protected void doSetRollbackOnly(DefaultTransactionStatus status) {
    DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)status.getTransaction();
    if (status.isDebug()) {
        this.logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() + "] rollback-only");
    }
 
    txObject.setRollbackOnly();
}

接着看方法doSetRollbackOnly中的DataSourceTransactionObject.setRollbackOnly()方法,如下所示:

public void setRollbackOnly() {
    this.getConnectionHolder().setRollbackOnly();
}

这里,可以看到最终设置的是connectionHolderrollbackonly属性。

5. functionOne方法尝试提交时的操作


我们首先看TransactionAspectSupport类中的commitTransactionAfterReturning方法,如下所示:

protected void commitTransactionAfterReturning(@Nullable TransactionAspectSupport.TransactionInfo txInfo) {
    if (txInfo != null && txInfo.getTransactionStatus() != null) {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
        }
 
        txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
    }
 
}

同样的,这里调用transactionManager进行提交。

紧着看AbstractPlatformTransactionManager中的commit方法,如下所示:

public final void commit(TransactionStatus status) throws TransactionException {
    if (status.isCompleted()) {
        throw new IllegalTransactionStateException("Transaction is already completed - do not call commit or rollback more than once per transaction");
    } else {
        DefaultTransactionStatus defStatus = (DefaultTransactionStatus)status;
        if (defStatus.isLocalRollbackOnly()) {
            if (defStatus.isDebug()) {
                this.logger.debug("Transactional code has requested rollback");
            }
 
            this.processRollback(defStatus, false);
        } else if (!this.shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
            if (defStatus.isDebug()) {
                this.logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
            }
 
            this.processRollback(defStatus, true);
        } else {
            this.processCommit(defStatus);
        }
    }
}

这个方法判断了一些无法提交的情况,程序这里走第二个分支,部分代码如下:

else if (!this.shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
    if (defStatus.isDebug()) {
        this.logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
    }
 
    this.processRollback(defStatus, true);
}

判断条件为:

  1. 全局不是rollbackonly时,也提交(这个可能是一个配置的参数,配合在rollbackonly的时候也提交,也就是出现现在这种情况后,不用回滚,直接提交)

  2. 并且全局是rollbackonly

进而看类DefaultTransactionStatus中的isGlobalRollbackOnly方法,如下所示:

public boolean isGlobalRollbackOnly() {
    return this.transaction instanceof SmartTransactionObject && ((SmartTransactionObject)this.transaction).isRollbackOnly();
}

这里又要满足两个条件:

  1. 这里的transactionDataSourceTransactionObject

DataSourceTransaction继承JdbcTransactionObjectSupport

JdbcTransactionObjectSupport又实现SmartTransactionObject,所以第一个条件满足。

  1. DatSourceTransactionObjectRollbackOnlygetset方法如下:
public void setRollbackOnly() {
    this.getConnectionHolder().setRollbackOnly();
}
 
public boolean isRollbackOnly() {
    return this.getConnectionHolder().isRollbackOnly();
}

之前functionTwo方法抛出异常时,就是调用的DataSourceTransactionObjectset方法设置rollbackonlytrue,现在再用get方法获取,只要是同一个connectionHolder

functionOne获取到的rollbackOnly就是true,就会触发回滚,执行this.processRollback(defStatus, true)

最后再次看类AbstractPlatformTransactionManager中的processRollback方法,如下所示:

private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
    try {
        boolean unexpectedRollback = unexpected;
 
        try {
            this.triggerBeforeCompletion(status);
            if (status.hasSavepoint()) {
                if (status.isDebug()) {
                    this.logger.debug("Rolling back transaction to savepoint");
                }
 
                status.rollbackToHeldSavepoint();
            } else if (status.isNewTransaction()) {
                if (status.isDebug()) {
                    this.logger.debug("Initiating transaction rollback");
                }
 
                this.doRollback(status);
            } else {
                if (status.hasTransaction()) {
                    if (!status.isLocalRollbackOnly() && !this.isGlobalRollbackOnParticipationFailure()) {
                        if (status.isDebug()) {
                            this.logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
                        }
                    } else {
                        if (status.isDebug()) {
                            this.logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
                        }
 
                        this.doSetRollbackOnly(status);
                    }
                } else {
                    this.logger.debug("Should roll back transaction but cannot - no transaction available");
                }
 
                if (!this.isFailEarlyOnGlobalRollbackOnly()) {
                    unexpectedRollback = false;
                }
            }
        } catch (Error | RuntimeException var8) {
            this.triggerAfterCompletion(status, 2);
            throw var8;
        }
 
        this.triggerAfterCompletion(status, 1);
        if (unexpectedRollback) {
            throw new UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only");
        }
    } finally {
        this.cleanupAfterCompletion(status);
    }
}

到这里unexpectedRollbacktrue,就抛出了Transaction rolled back because it has been marked as rollback-only这个异常了。

6. 总结


以上就是Transaction silently rolled back because it has been marked as rollback-only错误来分析spring boot的事务机制。

如果有不同意见的,可以在评论区中留言,大家共同进步。

如果你对Knife4j感兴趣,可以参考博文:https://blog.csdn.net/lvoelife/article/details/128114264

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

解决org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back beca 的相关文章

随机推荐

  • 有关const *、* const 、const * const 之间的区别

    1 const 表示指针指向的值不可变 但是指针可以重新赋新地址 include
  • Fiddler-抓包步骤详解

    一 工作原理介绍 1 Fiddler抓包是在 客户端和服务器之间建立一个代理服务器 监听本机发出的请求和服务器返回的响应结果 允许监视 设置断点 甚至修改输入输出数据 2 Fiddler使用的代理地址是127 0 0 1 端口是8888 它
  • 从“白人饭”到美味佳肴,拓世AI为你打造独一无二的饮食计划

    最近 白人饭 作为一种饮食方式在社交媒体上火了 成为打工人新的 午餐之光 所谓 白人饭 就是花最少的功夫准备仅仅能维系基本器官正常运作的食物 主打生吃或者简单炒 比如一个丹麦网友晒出的同事的午饭就是几根小胡萝卜和青菜 德国网友中午只吃一个白
  • Pycharm同一目录下py文件相互调用

    1 首先确保所在目录是Python Package而不是一般的New Stratch File 或者是普通的Directory也可以 Python Package下有 init py或自己建空的 init py 2 pycharm不会将当前
  • 免费JS富文本编辑器 总有一款会适合你

    什么是富文本编辑器 概述 javascript Text Editor 我们平常在页面上写文章 或者 后台发布文章内容 或内容描述等 一直都是用的是富文本编辑器 包括很多论坛发帖的文字窗口也是典型的富文本编辑器 它跟office中的word
  • linux C函数之stat函数

    1 函数功能 通过文件名filename获取文件信息 并保存在buf所指的结构体stat中 2 函数原型 函数头文件 include
  • 用C语言实现DFT算法

    一 简介 离散傅里叶变换 Discrete Fourier Transform 缩写为DFT 是傅里叶变换在时域和频域上都呈离散的形式 将信号的时域采样变换为其DTFT的频域采样 在形式上 变换两端 时域和频域上 的序列是有限长的 而实际上
  • vue.js element radio 单选框选中切换不同内容

    代码 span 分区类型 span
  • 如何检测linux系统下ftp服务器是否开启

    查看下端口是不是开启 netstat an grep 21 查看进程 ps ef grep ftp
  • RTX2080Ti-深度学习环境配置

    最近新购置了一台服务器 被环境折腾了半天 在这里纪念一下安装的心酸历程 环境配置 ubuntu18 04 cuda10 0 cudnn7 3 1 tensorflow gpu1 12 0 1 下载显卡驱动 cuda cudnn cuda10
  • 逍遥模拟器拷贝android根目录文件,逍遥android模拟器怎么导出APK文件

    1 运行SDK Manager 选择模拟器 并运行模拟器3 点击开始 运行 输入cmd 打开cmd窗口 输入cd C Program Files android sdk windows platform tools 进入platform t
  • Win11将WSL做SSH服务器,实现通过局域网SSH远程连接到WSL上,并且开机自动启动,手把手教学

    前言 本人的需求是经常需要将自己的台式机作为服务器使用 但是工作的主力又是笔记本 在公司使用服务器 在家使用台式机 笔记本台式同步代码数据很麻烦 于是产生了这种需求 利用台式机的WSL搭建SSH服务器 局域网内笔记本可直接ssh连接到WSL
  • 数据结构视频教程 -《[麻省理工学院]_算法导论_翻译版》

    整个视频打包下载地址 史上最全的数据结构视频教程系列分享之 麻省理工学院 算法导论 翻译版 转载请保留出处和链接 更多优秀资源请访问 我是码农 MIT无论是在美国还是全世界都有非常重要的影响力 培养了众多对世界产生重大影响的人士 是全球高科
  • mysql 创建用户,指定数据库,表的读写权限常用命令

    mysql 创建用户 指定数据库 表的读写权限常用命令 老是记不住mysql命令 然后网上找了点资料 然后记录下来 方便查询使用 1 远程登录mysql mysql h ip u root p 密码 2 创建用户 格式 grant 权限 o
  • git在项目已存在的情况下拉取某个文件

    git在项目已存在的情况下拉取某个文件 切换到本地该分支后 git checkout Garrick name 如果是某个文件 git checkout Garrick a txt 如果是真个文件夹 文件夹名字为a git checkout
  • 稳压二极管及特性介绍

    稳压二极管及特性介绍 稳压二极管是一种特殊工艺制造的面结型硅半导体二极管 此类二极管杂质浓度比较高 空间电荷区的电荷密度比较大 该区域狭窄 容易形成强电场 当反向电压来临时 反向电流急剧增加 产生反向击穿 稳压管核心理论 稳压管未工作时 处
  • sqli-labs-less-13 post传参+布尔盲注

    Less 13 post 布尔盲注 首先通过burp suite抓包获取报文体 利用harkbar插件进行注入 判断闭合方式 uname or 1 1 passwd submit Submit 发现闭合方式为 uname passwd su
  • 90后的头上少了头发,多了压力

    我已经不怎么笑了 内心也没有以前那么波动了 就像一潭死水 扔个石头都激不起水花 一个刚刚下班的网友这样形容工作后的自己 下了班回到家不就轻松了吗 不 其实只有加班后的回家路上 在地铁上那一个小时可以算得上轻松 虽然拥挤 可地铁上的通勤路 却
  • Python二级(10)——Python的计算机生态

    一 知识导图 二 计算思维 1 人类在认识世界 改造世界过程中表现出三种基 本的思维特征 以实验和验证为特征的实证思维 以物理学科为代表 以推理和演绎为特征的逻辑 思维 以数学学科为代表 以设计和构造为特征 的计算思维 以计算机学科为代表
  • 解决org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back beca

    文章目录 1 复现错误 2 分析错误 3 分析spring的事务机制 3 1 入口程序 3 2 事务管理的主方法 3 3 细究各对象的获取 4 functionTwo方法抛异常后的回滚操作 5 functionOne方法尝试提交时的操作 6