Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange behaviour with @Transactional(propagation=Propagation.REQUIRES_NEW)

Here is my problem :

I'm running a batch on a Java EE/Spring/Hibernate application. This batch calls a method1. This method calls a method2 which can throw UserException (a class extending RuntimeException). Here is how it looks like :

@Transactional
public class BatchService implements IBatchService {
 @Transactional(propagation=Propagation.REQUIRES_NEW)
 public User method2(User user) {
   // Processing, which can throw a RuntimeException
 }

 public void method1() {
   // ...
   try {
     this.method2(user);
   } catch (UserException e) {
     // ...
   }
   // ...
 }
}

The exception is catched as the execution continues, but at the end of method1 when the transaction is closed a RollbackException is thrown.

Here is the stack trace :

org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:476)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at $Proxy128.method1(Unknown Source)
at batch.BatchController.method1(BatchController.java:202)

When method2 is not throwing this exception, it works well.

What I have tried:

  • Setting @Transactional(noRollbackFor={UserException.class})) on method1
  • Try and catch in method2

But it didn't change anything.

As the exception is thrown in a different transaction where a rollback happened I don't understand why it doesn't work. I had a look at this : Jpa transaction javax.persistence.RollbackException: Transaction marked as rollbackOnly but it didn't really help me.

I will be very greatful if someone could give me a clue.

Update

I've made it work by setting propagation=Propagation.REQUIRES_NEW on the method called by method2 (which is actually the one which is sending the exception). This method is defined in a class very similar to my BatchService. So I don't see why it works on this level and not on method2.

  • I've set method2 as public as the annotation @Transactional is not taken into account if the method is private as said in the documentation :

The @Transactional annotation may be placed before an interface definition, a method on an interface, a class definition, or a public method on a class.

  • I also tried to use Exception instead of RuntimeException (as it is more appropriate) but it also didn't change anything.

Even if it is working the question remains open as it has a strange behaviour and I would like to understand why it's not acting like it should be.

like image 827
DessDess Avatar asked Apr 02 '13 15:04

DessDess


People also ask

What is @transactional propagation propagation Requires_new?

When the propagation is REQUIRES_NEW, Spring suspends the current transaction if it exists, and then creates a new one: @Transactional(propagation = Propagation.

What happens when a method annotated with @transaction propagation Requires_new is invoked?

The main difference between them is if a method in Spring Business Activity/DAO class is annotated with Propagation. REQUIRES_NEW, then when the execution comes to this method, it will create a new transaction irrespective of the existing transaction whereas if the method is annotated with Propagation.

What does the @transactional annotation mean?

The @Transactional annotation is the metadata that specifies the semantics of the transactions on a method. We have two ways to rollback a transaction: declarative and programmatic. In the declarative approach, we annotate the methods with the @Transactional annotation.

What is the transaction behavior of the propagation requires new mode?

REQUIRES_NEW behavior means that a new physical transaction will always be created by the container. The NESTED behavior makes nested Spring transactions to use the same physical transaction but sets savepoints between nested invocations so inner transactions may also rollback independently of outer transactions.


1 Answers

Spring transactions, by default, work by wrapping the Spring bean with a proxy which handles the transaction and the exceptions. When you call method2() from method1(), you're completely bypassing this proxy, so it can't start a new transaction, and you're effectively calling method2() from the same transaction as the one opened by the call to method1().

On the contrary, when you call a method of another injected bean from method1(), you're in fact calling a method on a transactional proxy. So if this alien method is marked with REQUIRES_NEW, a new transaction is started by the proxy, and you're able to catch the exception in method1() and resume the outer transaction.

This is described in the documentation.

like image 164
JB Nizet Avatar answered Oct 26 '22 20:10

JB Nizet