views:

413

answers:

2

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?
+2  A: 

Scroll a little more back in the log (or increase it's buffer-size) and you will see what exactly caused the exception.

If it happens not to be there, check the getMostSpecificCause() and getRootCause() methods of UnexpectedRollbackException- they might be useful.

Bozho
If the logs (with increased buffer size) contained the exact cause, I wouldn't have been asking it here. I want to understand the scenarios where this exception is thrown. Why is my setup relevant? a very common Hibernate + Spring configuration with HibernateTransactionManager by the way
Ehrann Mehdan
it is a common mistake to skip the root cause of transaction rollbacks, because it's very far behind in the stacktrace, and there are sometimes sql queries outputted in between. I asked for your setup because otherwise I (or anyone else) can't guess what could actually be causing this. Anyway, check my updated answer for another hint.
Bozho
@Bozho - +1 for update, to accept an answer though, I want to understand if the rollback occured in the database, or in the server (e.g. in the software transaction manager or in the database via a JDBC exception) maybe I'm not asking the right question?
Ehrann Mehdan
well, eventually, by calling getRootCause() and getCause() (whichever is not null), you can reach the real root exception, which will tell you the details you want (hopefully).
Bozho
+1  A: 

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

Ehrann Mehdan