Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UnexpectedRollbackException - a full scenario analysis

Tags:

All I know about this exception is from Spring's documentation and some forum posts with frostrated developers pasting huge stack traces, and no replies.

From Spring's documentation:

Thrown when an attempt to commit a transaction resulted in an unexpected rollback

I want to understand once and for all

  1. Exactly what causes it?

    • Where did the rollback occur? in the App Server code or in the Database?
    • Was it caused due to a specific underlying exception (e.g. something from java.sql.*)?
    • Is it related to Hibernate? Is it related to Spring Transaction Manager (non JTA in my case)?
  2. How to avoid it? is there any best practice to avoid it?

  3. How to debug it? it seems to be hard to reproduce, any proven ways to troubleshoot it?
like image 639
Eran Medan Avatar asked Jan 05 '10 15:01

Eran Medan


People also ask

What is UnexpectedRollbackException?

public class UnexpectedRollbackException extends TransactionException. Thrown when an attempt to commit a transaction resulted in an unexpected rollback.

How do you handle UnexpectedRollbackException?

Hence the UnexpectedRollbackException. To prevent this, the only thing you need to do is catch (and swallow, if you really want to ignore this problem) the original ConstraintViolationException WITHIN the transaction. Inside the saveMyData() for example would be ok.


1 Answers

I found this to be answering the rest of question: https://jira.springsource.org/browse/SPR-3452

I guess we need to differentiate between 'logical' transaction scopes and 'physical' transactions here...

What PROPAGATION_REQUIRED creates is a logical transaction scope for each method that it gets applied to. Each such logical transaction scope can individually decide on rollback-only status, with an outer transaction scope being logically independent from the inner transaction scope. Of course, in case of standard PROPAGATION_REQUIRED behavior, they will be mapped to the same physical transaction. So a rollback-only marker set in the inner transaction scope does affect the outer transaction's chance to actually commit. However, since the outer transaction scope did not decide on a rollback itself, the rollback (silently triggered by the inner transaction scope) comes unexpected at that level - which is why an UnexpectedRollbackException gets thrown.

PROPAGATION_REQUIRES_NEW, in contrast, uses a completely independent transaction for each affected transaction scope. In that case, the underlying physical transactions will be different and hence can commit or rollback independently, with an outer transaction not affected by an inner transaction's rollback status.

PROPAGATION_NESTED is different again in that it uses a single physical transaction with multiple savepoints that it can roll back to. Such partial rollbacks allow an inner transaction scope to trigger a rollback for its scope, with the outer transaction being able to continue the physical transaction despite some operations having been rolled back. This is typically mapped onto JDBC savepoints, so will only work with JDBC resource transactions (Spring's DataSourceTransactionManager).

To complete the discussion: UnexpectedRollbackException may also be thrown without the application ever having set a rollback-only marker itself. Instead, the transaction infrastructure may have decided that the only possible outcome is a rollback, due to constraints in the current transaction state. This is particularly relevant with XA transactions.

As I suggested above, throwing an exception at the inner transaction scope, then catching that exception at the outer scope and translating it into a silent setRollbackOnly call there should work for your scenario. A caller of the outer transaction will never see an exception then. Since you only worry about such silent rollbacks because of special requirements imposed by a caller, I would even argue that the correct architectural solution is to use exceptions within the service layer, and to translate those exceptions into silent rollbacks at the service facade level (right before returning to that special caller).

Since your problem is possibly not only about rollback exceptions, but rather about any exceptions thrown from your service layer, you could even use standard exception-driven rollbacks all the way throughout you service layer, and then catch and log such exceptions once the transaction has already completed, in some adapting service facade that translates your service layer's exceptions into UI-specific error states.

Juergen

like image 122
Eran Medan Avatar answered May 21 '23 11:05

Eran Medan