== rollback behaviour ==

  REQUIRED inner, outer rolls back
      outcome : IllegalStateException
      message : outer failed after the inner call returned
      rows    : 0  -> inner work rolled back

  REQUIRES_NEW inner, outer rolls back
      outcome : IllegalStateException
      message : outer failed after the inner call returned
      rows    : 1  -> inner work SURVIVED

  NESTED inner, outer rolls back
      outcome : NestedTransactionNotSupportedException
      message : Transaction manager does not allow nested transactions by default - specify 'nestedTransactionAllowed' property with value 'true'
      rows    : 0  -> inner work rolled back

  REQUIRED inner throws, outer catches it
      outcome : UnexpectedRollbackException
      message : Transaction silently rolled back because it has been marked as rollback-only
      rows    : 0  -> inner work rolled back

  REQUIRES_NEW inner throws, outer catches it
      outcome : returned normally
      rows    : 0  -> inner work rolled back

  NESTED inner throws, outer catches it
      outcome : returned normally
      rows    : 0  -> inner work rolled back

The third and fourth rows are the ones worth sitting with.

When a REQUIRED inner scope throws, it marks the SHARED transaction rollback-only
before the exception leaves it. The caller can catch the exception -- and does, and
returns normally -- but the transaction is already doomed, so the commit at the end
throws UnexpectedRollbackException. Catching the exception did not save the work; it
only moved the failure to a place with no useful stack trace.

With REQUIRES_NEW the inner scope had its own physical transaction, so its rollback
is contained and the caller's catch behaves the way the code reads.
