Strange behaviour with @Transactional(propagation=Propagation.REQUIRES_NEW)
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.
Comments
-
DessDess almost 2 years
Here is my problem :
I'm running a batch on a Java EE/Spring/Hibernate application. This batch calls a
method1
. This method calls amethod2
which can throwUserException
(a class extendingRuntimeException
). 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}))
onmethod1
- 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 bymethod2
(which is actually the one which is sending the exception). This method is defined in a class very similar to myBatchService
. So I don't see why it works on this level and not onmethod2
.- 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 ofRuntimeException
(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.
- Setting
-
DessDess about 11 yearsPerfect answer with a real understanding of what is happening, Thank you !
-
Wim Deblauwe about 9 yearsNote that if you need these self invocations to work, you should use AspectJ mode in Spring. See docs.spring.io/spring/docs/current/spring-framework-reference/…: "Consider the use of AspectJ mode (see mode attribute in table below) if you expect self-invocations to be wrapped with transactions as well. In this case, there will not be a proxy in the first place; instead, the target class will be weaved (that is, its byte code will be modified) in order to turn @Transactional into runtime behavior on any kind of method."
-
Vito over 8 yearsyeah,real understanding of the spring transaction... thank you