http://hsyd.iteye.com/blog/586772

错误信息: Transaction rolled back because it has been marked as rollback-only  原因:事务提交多次 检查代码 例:service嵌套service

=============================================================================


org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

 

我遇到这个异常出现的情况是这样的: 配置事务拦截处理如下

<bean id="txProxy" abstract="true"
     class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
     <property name="transactionManager" ref="transactionManager"/>
     <property name="transactionAttributes">
       <props>
         <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
         <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
         <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
         <prop key="query*">PROPAGATION_REQUIRED,readOnly</prop>
         <prop key="is*">PROPAGATION_REQUIRED,readOnly</prop>
         <prop key="*">PROPAGATION_REQUIRED,-java.lang.Exception</prop>
       </props>
     </property>
   </bean>

如在一个save方法中去调一个load方法,因为事务属性配置都为PROPAGATION_REQUIRED,

所以两方法使用的是同一事务,而load方法不执行提交,(关于Spring声明式事务管理源码解读之事务提交,可见

http://ahuaxuan.javaeye.com/blog/89072)如果load方法出现异常,则org.springframework.transaction.interceptor.

TransactionInterceptor的invoke处理后则会抛出异常.执行completeTransactionAfterThrowing(txInfo, ex);

public Object invoke(final MethodInvocation invocation) throws Throwable {
         // Work out the target class: may be <code>null</code>.
         // The TransactionAttributeSource should be passed the target class
         // as well as the method, which may be from an interface.
         Class targetClass = (invocation.getThis() != null ? invocation.getThis().getClass() : null);

         // If the transaction attribute is null, the method is non-transactional.
         final TransactionAttribute txAttr =
                 getTransactionAttributeSource().getTransactionAttribute(invocation.getMethod(), targetClass);
         final String joinpointIdentification = methodIdentification(invocation.getMethod());

         if (txAttr == null || !(getTransactionManager() instanceof CallbackPreferringPlatformTransactionManager)) {
             // Standard transaction demarcation with getTransaction and commit/rollback calls.
             TransactionInfo txInfo = createTransactionIfNecessary(txAttr, joinpointIdentification);
             Object retVal = null;
             try {
                 // This is an around advice: Invoke the next interceptor in the chain.
                 // This will normally result in a target object being invoked.
                 retVal = invocation.proceed();
             }
             catch (Throwable ex) {
                 // target invocation exception
                 completeTransactionAfterThrowing(txInfo, ex);
                 throw ex;
             }
             finally {
                 cleanupTransactionInfo(txInfo);
             }
             commitTransactionAfterReturning(txInfo);
             return retVal;
         }

         else {
             // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
             try {
                 Object result = ((CallbackPreferringPlatformTransactionManager) getTransactionManager()).execute(txAttr,
                         new TransactionCallback() {
                             public Object doInTransaction(TransactionStatus status) {
                                 TransactionInfo txInfo = prepareTransactionInfo(txAttr, joinpointIdentification, status);
                                 try {
                                     return invocation.proceed();
                                 }
                                 catch (Throwable ex) {
                                     if (txAttr.rollbackOn(ex)) {
                                         // A RuntimeException: will lead to a rollback.
                                         if (ex instanceof RuntimeException) {
                                             throw (RuntimeException) ex;
                                         }
                                         else {
                                             throw new ThrowableHolderException(ex);
                                         }
                                     }
                                     else {
                                         // A normal return value: will lead to a commit.
                                         return new ThrowableHolder(ex);
                                     }
                                 }
                                 finally {
                                     cleanupTransactionInfo(txInfo);
                                 }
                             }
                         });

                 // Check result: It might indicate a Throwable to rethrow.
                 if (result instanceof ThrowableHolder) {
                     throw ((ThrowableHolder) result).getThrowable();
                 }
                 else {
                     return result;
                 }
             }
             catch (ThrowableHolderException ex) {
                 throw ex.getCause();
             }
         }
     }completeTransactionAfterThrowing方法源码如下:   根据TransactionInfo的事务属性判断处理的异常是否需要做
rollback处理,从来根据事务状态来回滚事务.
    protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
         if (txInfo != null && txInfo.hasTransaction()) {
             if (logger.isTraceEnabled()) {
                 logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
                         "] after exception: " + ex);
             }
             if (txInfo.transactionAttribute.rollbackOn(ex)) {
                 try {
                     getTransactionManager().rollback(txInfo.getTransactionStatus());
                 }
                 catch (TransactionSystemException ex2) {
                     logger.error("Application exception overridden by rollback exception", ex);
                     ex2.initApplicationException(ex);
                     throw ex2;
                 }
                 catch (RuntimeException ex2) {
                     logger.error("Application exception overridden by rollback exception", ex);
                     throw ex2;
                 }
                 catch (Error err) {
                     logger.error("Application exception overridden by rollback error", ex);
                     throw err;
                 }
             }
             else {
                 // We don't roll back on this exception.
                 // Will still roll back if TransactionStatus.isRollbackOnly() is true.
                 try {
                     getTransactionManager().commit(txInfo.getTransactionStatus());
                 }
                 catch (TransactionSystemException ex2) {
                     logger.error("Application exception overridden by commit exception", ex);
                     ex2.initApplicationException(ex);
                     throw ex2;
                 }
                 catch (RuntimeException ex2) {
                     logger.error("Application exception overridden by commit exception", ex);
                     throw ex2;
                 }
                 catch (Error err) {
                     logger.error("Application exception overridden by commit error", ex);
                     throw err;
                 }
             }
         }
     } 
org.springframework.transaction.support.AbstractPlatformTransactionManager的rollback方法如下:
如果事务状态是未完成的则抛出IllegalTransactionStateException,否则则根据此事务状态处理事务回滚。
    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");
         }

         DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
         processRollback(defStatus);
     } 
执行processRollback方法,如果status有事务,事务是部分失败全局回滚。则执行实现类HibernateTransaction
Manager的doSetRollbackOnly()方法。
private void processRollback(DefaultTransactionStatus status) {
         try {
             try {
                 triggerBeforeCompletion(status);
                 if (status.hasSavepoint()) {
                     if (status.isDebug()) {
                         logger.debug("Rolling back transaction to savepoint");
                     }
                     status.rollbackToHeldSavepoint();
                 }
                 else if (status.isNewTransaction()) {
                     if (status.isDebug()) {
                         logger.debug("Initiating transaction rollback");
                     }
                     doRollback(status);
                 }
                 else if (status.hasTransaction()) {
                     if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
                         if (status.isDebug()) {
                             logger.debug(
                                     "Participating transaction failed - marking existing transaction as rollback-only");
                         }
                         doSetRollbackOnly(status);
                     }
                     else {
                         if (status.isDebug()) {
                             logger.debug(
                                     "Participating transaction failed - letting transaction originator decide on rollback");
                         }
                     }
                 }
                 else {
                     logger.debug("Should roll back transaction but cannot - no transaction available");
                 }
             }
             catch (RuntimeException ex) {
                 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
                 throw ex;
             }
             catch (Error err) {
                 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
                 throw err;
             }
             triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
         }
         finally {
             cleanupAfterCompletion(status);
         }
     } 
HibernateTransactionManager的doSetRollbackOnly()方法:将HibernateTransactionObject设置为rollbackonly.
    protected void doSetRollbackOnly(DefaultTransactionStatus status) {
         HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
         if (status.isDebug()) {
             logger.debug("Setting Hibernate transaction on Session [" +
                     SessionFactoryUtils.toString(txObject.getSessionHolder().getSession()) + "] rollback-only");
         }
         txObject.setRollbackOnly();
     } 
        public void setRollbackOnly() {
             getSessionHolder().setRollbackOnly();
             if (hasConnectionHolder()) {
                 getConnectionHolder().setRollbackOnly();
             }
         }

正如前面所说的, 两个方法:save,load同属于一个事务,而load事务产生异常,修改了sessionHolder和connection

Holder的rollbackonly属性为true.

而当save执行commit的时候,判断事务状态是否为全局回滚(就是判断HibernateTransactionObject的sessionHolder

和connectionHolder是否为rollbackonly),此时的rollbackonly属性则为true。处理完回滚后,继续判断事务状态是否为

新事务(因为save方法启用时是新建立的一个事务,而load方法则是使用同一事务),所以则抛出了UnexpectedRollbackException

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");
         }

         DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
         if (defStatus.isLocalRollbackOnly()) {
             if (defStatus.isDebug()) {
                 logger.debug("Transactional code has requested rollback");
             }
             processRollback(defStatus);
             return;
         }
         if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
             if (defStatus.isDebug()) {
                 logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
             }
             processRollback(defStatus);
             // Throw UnexpectedRollbackException only at outermost transaction boundary
             // or if explicitly asked to.
             if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
                 throw new UnexpectedRollbackException(
                         "Transaction rolled back because it has been marked as rollback-only");
             }
             return;
         }

         processCommit(defStatus);
     }

====================


原来是这样设置的:


    

<tx:attributes>
             <tx:method name="*" read-only="true"/>
         </tx:attributes>

发现selectA调用selectB,如果selectB抛出Exception,selectA中捕获Exception但是并不继续向外抛出,最后会出现错误。

 

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

selectB返回的时候,transaction被设置为rollback-only了,但是selectA正常消化掉,没有继续向外抛。

那么selectA结束的时候,transaction会执commit操作,但是transaction已经被设置为rollback-only了。

所以会出现这个错误。

有的同学说了,那不是没得搞了,service不能抛出异常,或者不能拦截异常了?

其实不然,其实错误不在这里,而是select这种操作为什么要启动事务呢?

调整好问题,找解决方案,问题就出现在propagation="REQUIRED"这个属性上。

标准文档上这样写:

MANDATORY 

          Support a current transaction, throw an exception if none exists.

NESTED 

          Execute within a nested transaction if a current transaction exists, behave like PROPAGATION_REQUIRED else.

NEVER 

          Execute non-transactionally, throw an exception if a transaction exists.

NOT_SUPPORTED 

          Execute non-transactionally, suspend the current transaction if one exists.

REQUIRED 

          Support a current transaction, create a new one if none exists.

REQUIRES_NEW 

          Create a new transaction, suspend the current transaction if one exists.

SUPPORTS 

          Support a current transaction, execute non-transactionally if none exists.

 

看来我们需要如下修改:

    

<tx:attributes>
            <tx:method name="*" read-only="true" propagation="NOT_SUPPORTED"/>
        </tx:attributes>

这样select这样的检索操作根本就不启动事务了,而且在有事务的方法中也是可以正常调用select方法的。

现在就没问题了。

但是现在出现了另外一个问题,就是,如果在一个事物内对db进行操作,然后在出事物之前对刚才db操作的数据进行select是获取不到修改结果的,为什么呢?因为not——supported是会在执行select之前挂起原有事物,不在原有事物内,当然无法获得修改后的数据。

怎么办?改成supports:

    

<tx:attributes>
            <tx:method name="*" read-only="true" propagation="SUPPORTS"/>
        </tx:attributes>

这个状态用一句话概括就是“有则加入事物,无也不创建事物”。