简体   繁体   中英

How to make Spring @Transactional roll back on all uncaught exceptions?

My Spring/Java web application has @Transactional services that can touch the database:

@Transactional
public class AbstractDBService  { ... }

Desired functionality is for any uncaught throwable that propagates up beyond the service layer to cause a rollback. Was a bit surprised this isn't the default behaviour but after a bit of googling tried:

@Transactional(rollbackFor = Exception.class)

This seems to work except when an exception is deliberately swallowed and not rethrown. (The particular case is when an entity is not found. Guess this could be redesigned to not throw an Exception but expect there will inevitably be others - eg one that springs to mind is an InterruptedException when using Thread.sleep() ). Then Spring complains:

org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly ...truncated.. Caused by: javax.persistence.RollbackException: Transaction marked as rollbackOnly at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:58) at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517)

Am I missing something here?... Is there a way to tell Spring to rollback on all uncaught throwables ?

If you want to rollback on all uncaught Throwables, you can specify that in the annotation:

@Transactional(rollbackFor = Throwable.class)

By default Spring doesn't rollback for Error subclasses, probably because it seems doubtful once an Error is thrown that the JVM will be in a good enough state to do anything about it anyway, at that point the transaction can just time out. (If you try to rollback when an OutOfMemoryError is raised, the most likely outcome is another OutOfMemoryError.) So you may not gain much with this.

When you mention the case of swallowing an exception, there's no way Spring can be expected to know about it because the exception is not finding its way to Spring's proxy (which is implementing the transactional functionality). This is what happens in your RollbackException example, Hibernate has figured out the transaction needs to rollback but Spring didn't get the memo because somebody ate the exception. So Spring isn't rolling the transaction back, it thinks everything is ok and tries to commit, but the commit fails due to Hibernate having marked the transaction rollback-only.

The answer is to not swallow those exceptions but let them be thrown; making them unchecked is supposed to make it easier for you to do the right thing. There should be an exception handler set up to receive exceptions thrown from the controllers, most exceptions thrown at any level of the application can be caught there and logged.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM