java runtime_error ai_generated true

org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction was rolled back because it has been marked as rollback-only

ID: java/transaction-rolled-back-deadlock

Also available as: JSON · Markdown · 中文
85%Fix Rate
87%Confidence
1Evidence
2024-01-10First Seen

Version Compatibility

VersionStatusIntroducedDeprecatedNotes
Spring Boot 2.7.x active
Spring Boot 3.2.x active
Hibernate 5.6.x active
Hibernate 6.3.x active

Root Cause

This error occurs when a transaction is marked for rollback-only (e.g., due to a data integrity violation or optimistic locking failure) but the outer transaction context attempts to commit it, typically in a nested transaction or service layer where an exception was caught and swallowed.

generic

中文

当事务被标记为仅回滚(例如由于数据完整性违规或乐观锁定失败),但外部事务上下文尝试提交它时发生,通常在嵌套事务或服务层中捕获并吞没了异常。

Official Documentation

https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/tx-propagation.html

Workarounds

  1. 90% success Use REQUIRES_NEW propagation for the inner method to create a separate transaction: `@Transactional(propagation = Propagation.REQUIRES_NEW)` so that the inner transaction's rollback does not affect the outer transaction.
    Use REQUIRES_NEW propagation for the inner method to create a separate transaction: `@Transactional(propagation = Propagation.REQUIRES_NEW)` so that the inner transaction's rollback does not affect the outer transaction.
  2. 85% success Check for rollback-only status before committing: `TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();` and then handle the error gracefully in the outer method by calling `TransactionAspectSupport.currentTransactionStatus().flush()`.
    Check for rollback-only status before committing: `TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();` and then handle the error gracefully in the outer method by calling `TransactionAspectSupport.currentTransactionStatus().flush()`.
  3. 80% success Refactor the code to avoid swallowing exceptions: ensure that any exception that triggers a rollback is rethrown to the outer transactional boundary, e.g., `catch (DataIntegrityViolationException e) { throw new RuntimeException(e); }`.
    Refactor the code to avoid swallowing exceptions: ensure that any exception that triggers a rollback is rethrown to the outer transactional boundary, e.g., `catch (DataIntegrityViolationException e) { throw new RuntimeException(e); }`.

中文步骤

  1. Use REQUIRES_NEW propagation for the inner method to create a separate transaction: `@Transactional(propagation = Propagation.REQUIRES_NEW)` so that the inner transaction's rollback does not affect the outer transaction.
  2. Check for rollback-only status before committing: `TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();` and then handle the error gracefully in the outer method by calling `TransactionAspectSupport.currentTransactionStatus().flush()`.
  3. Refactor the code to avoid swallowing exceptions: ensure that any exception that triggers a rollback is rethrown to the outer transactional boundary, e.g., `catch (DataIntegrityViolationException e) { throw new RuntimeException(e); }`.

Dead Ends

Common approaches that don't work:

  1. Adding @Transactional(rollbackFor = Exception.class) to all methods 80% fail

    This does not prevent the rollback-only marking; it only ensures exceptions trigger rollback. The issue is that an exception was caught and the transaction was marked rollback-only, then a commit is attempted.

  2. Catching the exception in the outer method and ignoring it 90% fail

    The transaction is already marked rollback-only by the inner method; ignoring the exception does not reset the transaction status.

  3. Increasing the transaction timeout to avoid premature rollback 70% fail

    The rollback is due to a data error (e.g., constraint violation), not a timeout. Increasing timeout does not address the root cause.