16
@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.

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
ThermalEagle
  • 191
  • 1
  • 1
  • 5
  • 1
    Did you set globalRollbackOnParticipationFailure to false E.g. ` ` – sol4me Jan 08 '15 at 21:49
  • pretty sure sol4me is on the right track - see http://stackoverflow.com/a/11205537/1594449 – gknicker Jan 08 '15 at 22:01
  • 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 Jan 08 '15 at 22:09
  • 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! – ThermalEagle Jan 08 '15 at 22:29
  • are `methodA` and `MethodB` on the same object/service? – M. Deinum Jan 09 '15 at 06:46
  • @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..?? – Ankur Singhal Jan 09 '15 at 06:47
  • 1
    @ankur-singhal - would it matter who starts the transaction for methodA since methodB is starting a new transaction? – Andy Dufresne Jan 09 '15 at 07:05
  • @AndyDufresne yes, it does not matter since `methodB` starts its own transaction, – Ankur Singhal Jan 09 '15 at 07:08
  • Is the `dao.insert(e)` annotated with `@Transactional`? If so, it should also have `noRollbackFor` to not mark whole transaction to rollback. – Cloudanger Sep 10 '19 at 22:21

1 Answers1

31

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));
}
Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
  • Well explained with an example – Andy Dufresne Jan 09 '15 at 07:08
  • 1
    @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` – Ankur Singhal Jan 09 '15 at 07:13
  • 1
    Even if there's a new transaction because of Propagation.REQUIRES_NEW, the same principle applies to the Hibernate Session. – Vlad Mihalcea Jan 09 '15 at 07:21
  • @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? – Oleg Kuts Dec 10 '20 at 08:01
  • That's right. You can use it for when the cache is not available, but you could still serve the request from the DB. – Vlad Mihalcea Dec 10 '20 at 12:00
  • @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`. – Oleg Kuts Dec 10 '20 at 13:27
  • Very interesting use case. You should write an article about it. I'm interested in reading more about it. – Vlad Mihalcea Dec 10 '20 at 14:09
  • 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. – Olgun Kaya Apr 22 '22 at 16:33