Spring @Transactional Not Auto Rolling Back During Unchecked Exception

Spring: automatic rollback on checked exceptions

You can't do it for application level with @Transactional , but you can :

variant 1 : extend @Transactional annotation and put it as default value for rollbackfor. But set rollbackFor unchecked exceptions only that you need .With this you can control rollbacks only for case that you sure , and avoid copy past of @Transactional(rollbackFor =MyCheckedException.class)

Like:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(rollbackFor=MyCheckedException.class)
public @interface TransactionalWithRollback {
}

And use this annotation instead of standard @Transactional.

variant 2 : you can create extension from AnnotationTransactionAttributeSource and override method determineTransactionAttribute:

protected TransactionAttribute  determineTransactionAttribute(AnnotatedElement ae)
//Determine the transaction attribute for the given method or class.

TransactionAttribute see TransactionAttribute api , there is a method

boolean rollbackOn(Throwable ex) Should we roll back on the given exception?

protected TransactionAttribute determineTransactionAttribute(
AnnotatedElement ae) {
return new DelegatingTransactionAttribute(target) {
@Override
public boolean rollbackOn(Throwable ex) {
return (check is exception type as you need for rollback );
}
};

}

Second approach is not so good as first as you do it really global for transaction manager. Better use custom annotation as you can control it any apply only for methods/classes where you really need it. But if you need it in any case use second variant , it will be your default transnational behavior.

Spring boot @Transactional not rolling back the database inserts

There are several things that break proper transactions in Spring

  1. Your service method is private
  2. You are catching and swallowing exceptions

private method

The fact that your PublicationServiceImpl save method is private basically makes the @Transactional on that method useless. As a private method cannot be proxied, no transactions will apply. Even if it would be public it wouldn't work as you are calling the method from within the same object, hence the transactionality of that method applies.

To fix, make your method public and call the save method from an other class (or make the actual method that is calling save have the proper @Transactional.

The fact that is doesn't work is due to the type op AOP being used, by default Spring will use proxies and this is a drawback of using proxy based AOP.

Another solution to make it work with private methods is to switch to full-blown AspectJ with either compile-time or load-time weaving of the classes. Both require additional setup and that can be tedious.

Catch and swallow exceptions

You have in both your repository as well as your service a try/catch block. Each of those catches and swallows the exceptions (they are logged but not re-thrown).

For transactions to work properly it needs to see the exceptions. The fact that you are catching and swallowing them, makes the transaction aspect not see them and instead of doing a rollback, do a commit. For the transaction aspect everything is ok because there was no exception.

To fix, remove either the try/catch or rethrow the exceptions.

why does transaction roll back on RuntimeException but not SQLException

This is defined behaviour. From the docs:

Any RuntimeException triggers rollback, and any checked Exception does not.

This is common behaviour across all Spring transaction APIs. By default, if a RuntimeException is thrown from within the transactional code, the transaction will be rolled back. If a checked exception (i.e. not a RuntimeException) is thrown, then the transaction will not be rolled back.

The rationale behind this is that RuntimeException classes are generally taken by Spring to denote unrecoverable error conditions.

This behaviour can be changed from the default, if you wish to do so, but how to do this depends on how you use the Spring API, and how you set up your transaction manager.

Spring transaction: rollback on Exception or Throwable

As I understand catching Error will help us behave correctly even when something really bad happen. Or maybe it wouldn't help?

You don't need to explicitly specify rollbackFor = Throwable.class, because spring will by default rollback the transaction if an Error occurs.

See 12.5.3 Rolling back a declarative transaction

In its default configuration, the Spring Framework's transaction infrastructure code only marks a transaction for rollback in the case of runtime, unchecked exceptions; that is, when the thrown exception is an instance or subclass of RuntimeException. (Errors will also - by default - result in a rollback). Checked exceptions that are thrown from a transactional method do not result in rollback in the default configuration.

Or take a look at the DefaultTransactionAttribute

public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}

Transaction is not getting rolled back though exception is thrown

The Spring documentation says the following:

While the EJB default behavior is for the EJB container to automatically roll back the transaction on a system exception (usually a runtime exception), EJB CMT does not roll back the transaction automatically on an application exception (that is, a checked exception other than java.rmi.RemoteException). While the Spring default behavior for declarative transaction management follows EJB convention (roll back is automatic only on unchecked exceptions), it is often useful to customize this.

And

In its default configuration, the Spring Framework’s transaction
infrastructure code only marks a transaction for rollback in the case
of runtime, unchecked exceptions; that is, when the thrown exception
is an instance or subclass of RuntimeException. ( Errors will also -
by default - result in a rollback). Checked exceptions that are thrown
from a transactional method do not result in rollback in the default
configuration

see in 16.5.3: https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html

This says that the default behavior of the transaction will only rollback for RuntimeExceptions. If you have a own business exception (could be a checked excpetion), you have to explicitly name the exception class the transaction should rollback for:

@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = YOUREXCEPTION.class)

Spring Transaction - automatic rollback of previous db updates when one db update failes

If you use declarative transaction management, you can lose most of this boilerplate:

TestDaoImpl:

private SessionFactory sessionFactory;

public void setSessionFactory(SessionFactory f){
this.sessionFactory = f;
}

public void saveOrUpdate(BaseDomainModel baseObject) {
Session session = sessionFactory.getCurrentSession();
session.saveOrUpdate(baseObject);
}

And you can control the transaction handling from the service layer using @Transactional (or xml configuration)

TestServiceImpl:

private TestDao testDao;

public void setTestDao(TestDao d){
this.testDao = d;
}

@Transactional // one transaction for multiple operations
public void someServiceMethod(Collection<BaseDomainModel> data){
for(BaseDomainModel baseObject : data)
testDao.saveOrUpdate(baseObject);
}

Reference:

  • Implementing DAOs based on plain
    Hibernate 3 API

Spring MVC + Hibernate @Transactional not Rollinback after exception

Your transaction is not getting rollback because there is no exception thrown , in other words saveUpdateDeleteTest is catching the exception, thats why spring transactional proxy cannot detect any exception and Hence no rollback. Remove the catch block and you will see that transaction will rollback . PLease note that spring transaction rollback follows EJB Conventation i.e.

While the EJB default behavior is for the EJB container to
automatically roll back the transaction on a system exception (usually
a runtime exception), EJB CMT does not roll back the transaction
automatically on an application exception (that is, a checked
exception other than java.rmi.RemoteException). While the Spring
default behavior for declarative transaction management follows EJB
convention (roll back is automatic only on unchecked exceptions), it
is often useful to customize this.

So in your case you need to customize if you want the transaction to be rolled back on any exception, like this:

@Transactional(rollbackFor = Exception.class)


Related Topics



Leave a reply



Submit