Spring always creates the new Transaction for REQUIRES_NEW, if an already existing transaction is present or not. but what is the situation where spring does not create new and instead participates in existing transactions?
Below is the situation where new transaction is not getting created and instead participates in existing.
All the methods, mainMethod, methodA(), methodB() and logExceptionMethod() are in different beans, Ideally, When methodB() fails, exception will get caught in catch block, and logExceptionMethod() will audit the log by creating new transaction.
But here new transaction is not started for the logExceptionMethod() method even though, this method is annotated with requires_new and is Public and in different bean, instead it participates in existing transaction which result in rollback
mainMethod - Propagation.REQUIRES methodA - Propagation.REQUIRES_NEW methodB - not annotated logExceptionMethod - Propagation.REQUIRES_NEW
Flow
@Transactional
mainMethod() {
try{
methodA();
} catch(Exception e) {
throw e;
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
methodA(){
try{
methodB()
}
catch(Exception e) {
throw e;
}
}
methodB(){
try {
// api call gives exception
}
catch(Exception e) {
logExceptionMethod();
throw e;
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
logExceptionMethod() {
// create log entity
// store entity in db
}
Logs
DEBUG o.s.orm.jpa.JpaTransactionManager - 1F2EA187-C884-461C-8183-328A4B7621D0 - Creating new transaction with name [mainMethod]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
DEBUG o.s.orm.jpa.JpaTransactionManager - 1F2EA187-C884-461C-8183-328A4B7621D0 - Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@53f498b2]
TRACE o.s.t.i.TransactionInterceptor - 1F2EA187-C884-461C-8183-328A4B7621D0 - Getting transaction for [mainMethod]
TRACE o.s.d.r.c.s.TransactionalRepositoryProxyPostProcessor$RepositoryAnnotationTransactionAttributeSource - 1F2EA187-C884-461C-8183-328A4B7621D0 - Adding transactional method 2023-08-11 03:04:13 [qtp1482177069-1382]
TRACE o.s.b.f.s.DefaultListableBeanFactory - 1F2EA187-C884-461C-8183-328A4B7621D0 - Returning cached instance of singleton bean 'transactionManager'
DEBUG o.s.orm.jpa.JpaTransactionManager - 1F2EA187-C884-461C-8183-328A4B7621D0 - Found thread-bound EntityManager [SessionImpl(1543398267<open>)] for JPA transaction
INFO - 1F2EA187-C884-461C-8183-328A4B7621D0 - Current transaction : mainMethod
DEBUG o.s.orm.jpa.JpaTransactionManager - 1F2EA187-C884-461C-8183-328A4B7621D0 - Found thread-bound EntityManager [SessionImpl(1543398267<open>)] for JPA transaction
DEBUG o.s.orm.jpa.JpaTransactionManager - 1F2EA187-C884-461C-8183-328A4B7621D0 - Suspending current transaction, creating new transaction with name [methodA]
DEBUG o.s.orm.jpa.JpaTransactionManager - 1F2EA187-C884-461C-8183-328A4B7621D0 - Opened new EntityManager [SessionImpl(1186799888<open>)] for JPA transaction
DEBUG o.s.orm.jpa.JpaTransactionManager - 1F2EA187-C884-461C-8183-328A4B7621D0 - Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@54c9937c]
TRACE o.s.t.i.TransactionInterceptor - 1F2EA187-C884-461C-8183-328A4B7621D0 - Getting transaction for [methodA]
INFO- 1F2EA187-C884-461C-8183-328A4B7621D0 - Current transaction : methodA
ERROR - 1F2EA187-C884-461C-8183-328A4B7621D0 - Exception Occured
TRACE o.s.d.r.c.s.TransactionalRepositoryProxyPostProcessor$RepositoryAnnotationTransactionAttributeSource - 1F2EA187-C884-461C-8183-328A4B7621D0 - Adding transactional method 'org.springframework.data.jpa.repository.support.SimpleJpaRepository.save' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
TRACE o.s.b.f.s.DefaultListableBeanFactory - 1F2EA187-C884-461C-8183-328A4B7621D0 - Returning cached instance of singleton bean
'transactionManager'
DEBUG o.s.orm.jpa.JpaTransactionManager - 1F2EA187-C884-461C-8183-328A4B7621D0 - Found thread-bound EntityManager [SessionImpl(1186799888<open>)] for JPA transaction
DEBUG o.s.orm.jpa.JpaTransactionManager - 1F2EA187-C884-461C-8183-328A4B7621D0 - Participating in existing transaction
INFO - 1F2EA187-C884-461C-8183-328A4B7621D0 - Added entry in log
TRACE o.s.t.i.TransactionInterceptor - 1F2EA187-C884-461C-8183-328A4B7621D0 - Completing transaction for [methodA] after exception:
DEBUG o.s.orm.jpa.JpaTransactionManager - 1F2EA187-C884-461C-8183-328A4B7621D0 - Initiating transaction rollback
DEBUG o.s.orm.jpa.JpaTransactionManager - 1F2EA187-C884-461C-8183-328A4B7621D0 - Rolling back JPA transaction on EntityManager [SessionImpl(1186799888<open>)]
DEBUG o.s.orm.jpa.JpaTransactionManager - 1F2EA187-C884-461C-8183-328A4B7621D0 - Closing JPA EntityManager [SessionImpl(1186799888<open>)] after transaction
DEBUG o.s.orm.jpa.JpaTransactionManager - 1F2EA187-C884-461C-8183-328A4B7621D0 - Resuming suspended transaction after completion of inner transaction
TRACE o.s.t.i.TransactionInterceptor - 1F2EA187-C884-461C-8183-328A4B7621D0 - Completing transaction for [mainMethod] after exception:
DEBUG o.s.orm.jpa.JpaTransactionManager - 1F2EA187-C884-461C-8183-328A4B7621D0 - Initiating transaction rollback
DEBUG o.s.orm.jpa.JpaTransactionManager - 1F2EA187-C884-461C-8183-328A4B7621D0 - Rolling back JPA transaction on EntityManager [SessionImpl(1543398267<open>)]
DEBUG o.s.orm.jpa.JpaTransactionManager - 1F2EA187-C884-461C-8183-328A4B7621D0 - Not closing pre-bound JPA EntityManager after transaction
Now I can use specific noRollbackFor property to prevent rollback the transaction, but still, the main question remains is
Why is a new transaction not getting created?
The issue stems from the fact that transactional properties are applied only when a proxy bean is created. In this case, it appears that a proxy is not being created, leading to the absence of a new transaction.
The logs indicate that no proxy is created for the class containing the HelperC where logExceptionMethod is present.
(printed with the help of (ApplicationContext.getBean(className).getClass().getName()) )
INFO 6F28649F-35D3-4382-9BA8-1AEAFA6E5FF3 ###classname: HelperC
However, CGLIB proxies are created for classes MainClass and SecondClass, where mainMethod and methodA are present.
INFO 8CBCB131-A16B-4F0D-9C17-AED5E15303CE - ###classname: MainClass$$EnhancerBySpringCGLIB$$b32bbff5
INFO 6F28649F-35D3-4382-9BA8-1AEAFA6E5FF3 - ###classname: SecondClass$$EnhancerBySpringCGLIB$$4c237172
Why is the proxy not getting created?
The logs suggest that the bean HelperC is not eligible for processing by all BeanPostProcessors, specifically, it is not eligible for auto-proxying.
INFO o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'helperA' of type [HelperA] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
INFO o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'helperB' of type [HelperB] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
INFO o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'helperC' of type [HelperC] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
This may be due to several reasons: HelperC is considered an infrastructure bean, which is a low-level bean used by the Spring framework itself. The count of registered bean post-processors (beanFactory.getBeanPostProcessorCount()) is less than a certain target count. HelperC is not an instance of BeanPostProcessor. This is determined by the PostProcessorRegistrationDelegate$BeanPostProcessorChecker class.
Additionally, the methodSecurityConfig class extends below class, which Configuration does not allow to create a proxy and defines beans as low-level beans (infrastructure beans)
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class GlobalMethodSecurityConfiguration
which prevents the creation of proxies. The methodSecurityConfig class autowires HelperA, which in turn autowires HelperB, eventually leading to the class containing logExceptionMethod, HelperC, which doesn't get proxied due to these rules.
Why is HelperA used in methodSecurityConfig in my case?
HelperA is used in methodSecurityConfig due to some business logic requirements. As a result, methodA from HelperA is autowired, leading to the autowiring of HelperB and subsequently HelperC, which contains logExceptionMethod. However, Spring Security configuration prevents the creation of proxies for these classes.
Solution:
To resolve this issue, I followed the below steps
Since HelperA is causing the issue by triggering the auto-wiring chain that leads to logExceptionMethod, I tried removing the dependency on HelperA from methodSecurityConfig. This would break the chain and potentially allow the creation of proxies for the relevant classes.
Alternatively, I can shift the logExceptionMethod to some other class as well, which is not getting used in the security context because by default beans used in the security context are not allowed to create proxies
In summary, the bean responsible for creating the transaction for logExceptionMethod is not being proxied. This is because it is being autowired in the security context, and the configuration in the parent class restricts the creation of proxies in this context.
https://www.baeldung.com/spring-not-eligible-for-auto-proxying