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:
@Transactional(noRollbackFor={UserException.class}))
on method1
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
.
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.
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.
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.