異常堆棧:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
日常工作中遇到如上錯誤,總結如下:
發生異常的場景描述:在使用Spring事務時,在一個事務A中又開了一個事務B(即存在嵌套事務),當事務B發生異常時,將異常catch後事務B會進行回滾操作,若此時異常被直接吃掉(即事務A無法得知發生過異常),則事務A會拋出如上異常。
根本原因:spring事務發生嵌套時,默認的傳播機制是“PROPAGATION_REQUIRED” ,“PROPAGATION_REQUIRED”表示如果當前沒有事務,則新建一個事務;若當前存在事務則加入到此事務中。當使用此傳播機制時,針對上述場景,事務A和事務B其實是同一個事務,當事務B回滾時已經將事務標記爲已回滾,此時當事務A再次執行conmint時就會報上述異常。
Spring事務conmint源碼如下:
/** * This implementation of commit handles participating in existing * transactions and programmatic rollback requests. * Delegates to <code>isRollbackOnly</code>, <code>doCommit</code> * and <code>rollback</code>. * @see org.springframework.transaction.TransactionStatus#isRollbackOnly() * @see #doCommit * @see #rollback */ 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); }
Spring事務傳播機制彙總如下:
- PROPAGATION_REQUIRED:如果當前沒有事務,就新建一個事務,如果已經存在一個事務,就加入到這個事務中。默認策略
- PROPAGATION_SUPPORTS:支持當前事務,如果當前沒有事務,就以非事務方式執行。
- PROPAGATION_MANDATORY:使用當前的事務,如果當前沒有事務,就拋出異常。
- PROPAGATION_REQUIRES_NEW:新建事務,如果當前存在事務,把當前事務掛起。
- PROPAGATION_NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
- PROPAGATION_NEVER:以非事務方式執行,如果當前存在事務,則拋出異常。
- PROPAGATION_NESTED:如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。