@Transactional (noRollbackFor=RuntimeException.class) does not prevent rollback on RuntimeException

24,506

Once an exception is caught, the Hibernate Session should be discarded and the transaction should be rolled back:

If the Session throws an exception, the transaction must be rolled back and the session discarded. The internal state of the Session might not be consistent with the database after the exception occurs.

So, noRollbackFor applies to your Service and DAO layer that might throw an exception. Let's say you have a gatewayService that write to a Database through a Hibernate DAO and also sends an email through an emailService. If the emailService throws a SendMailFailureException you can instruct the gatewayService not to roll back when it will catch this exception:

@Transactional(noRollbackFor=SendMailFailureException.class)
public void saveAndSend(Entity e){
   dao.save(e);
   emailService.send(new Email(e));
}
Share:
24,506
ThermalEagle
Author by

ThermalEagle

Updated on September 26, 2020

Comments

  • ThermalEagle
    ThermalEagle over 3 years
    @Transactional (noRollbackFor=RuntimeException.class)
    public void methodA (Entity e){
       service.methodB(e);
    }
    

    ---service method below---

    @Transactional (propagation=Propagation.REQUIRES_NEW, noRollbackFor=RuntimeException.class)
    public void methodB (Entity e){
       dao.insert(e);
    }
    

    When dao.insert(e) in methodB() causes a primary key violation and throws a ConstraintViolationException, which is a subclass of RuntimeException, I would expect the transaction to still commit because of the noRollbackFor property I used. But I observed that the outer transaction (on methodA) is still being rolled back by the HibernateTransactionManager with the message

    org.springframework.transaction.UnexpectedRollback Exception: Transaction rolled back because it has been marked as rollback-only

    I've found similar questions reported but not exactly this one.

    • sol4me
      sol4me over 9 years
      Did you set globalRollbackOnParticipationFailure to false E.g. <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransacti‌​onManager"> <property name="globalRollbackOnParticipationFailure" value="false" /> <property name="sessionFactory" ref="sessionFactory" /> </bean>
    • gknicker
      gknicker over 9 years
      pretty sure sol4me is on the right track - see stackoverflow.com/a/11205537/1594449
    • ThermalEagle
      ThermalEagle over 9 years
      Sounds like a great idea but I am not keen to change the setting of globalRollbackOnParticipationFailure because I am enhancing an existing monolithic piece of code that already uses the same transaction manager, and I would want to keep the existing code unaffected by my change - this does not appear feasible by changing the tx manager config. Can I do something that will be entirely specific to my change?
    • ThermalEagle
      ThermalEagle over 9 years
      just for your information, I also tried catching the RuntimeException thrown in dao.insert(e); within methodB and rethrowing it out after wrapping it in a checked exception. I changed the noRollbackFor to match the checked exception used. However, this made no difference - the outer transaction in methodA was still rolled back!
    • M. Deinum
      M. Deinum over 9 years
      are methodA and MethodB on the same object/service?
    • Ankur Singhal
      Ankur Singhal over 9 years
      @ThermalEagle how is the transaction started/propagated for methodA, Also have a look at the queries being fired at the back, does method A is trying to insert something as well during flushing..??
    • Andy Dufresne
      Andy Dufresne over 9 years
      @ankur-singhal - would it matter who starts the transaction for methodA since methodB is starting a new transaction?
    • Ankur Singhal
      Ankur Singhal over 9 years
      @AndyDufresne yes, it does not matter since methodB starts its own transaction,
    • Cloudanger
      Cloudanger over 4 years
      Is the dao.insert(e) annotated with @Transactional? If so, it should also have noRollbackFor to not mark whole transaction to rollback.
  • Andy Dufresne
    Andy Dufresne over 9 years
    Well explained with an example
  • Ankur Singhal
    Ankur Singhal over 9 years
    @AndyDufresne what my understanding is - Method B has its own transaction, method A transaction will be suspended , so as proxy, proxy called it target method method B, and if any exception occurs for which no rollback is configured, only this transaction will be take care of, then it will resume the transaction of method A
  • Vlad Mihalcea
    Vlad Mihalcea over 9 years
    Even if there's a new transaction because of Propagation.REQUIRES_NEW, the same principle applies to the Hibernate Session.
  • Oleg Kuts
    Oleg Kuts over 3 years
    @VladMihalcea so noRollBackFor works only for exceptions that are not database\hibernate specific ones? So say DataIntegrityViolationException will rollback in any case? Also as I understand even having Propagation.REQUIRES_NEW will not help as hibernate will mark all suspended transaction in the chain as rollback-only, because hibernate marks whole Session as rollback?
  • Vlad Mihalcea
    Vlad Mihalcea over 3 years
    That's right. You can use it for when the cache is not available, but you could still serve the request from the DB.
  • Oleg Kuts
    Oleg Kuts over 3 years
    @VladMihalcea looks like propagation.not_supported did the trick. Original transaction gets suspended, and is not going to roll back even if method marked with not_supported throws DataIntegrityViolationException.
  • Vlad Mihalcea
    Vlad Mihalcea over 3 years
    Very interesting use case. You should write an article about it. I'm interested in reading more about it.
  • Olgun Kaya
    Olgun Kaya about 2 years
    This helped me a lot. I first resist to use it like feeling a bit cheating. Then, I read the example and convinced. My case was, reset password token expired (no scheduler used for the sake of performance) but status still seems to be legit. I first check if token, if status legit but date expired; then update token to status expired too and throw exception (AlreadyUsedException) for client to take appropriate action. Without this property it was failing.