Wednesday, July 13, 2016

spring multiple transaction manager issue CannotCreateTransactionException


Problem

In this tutorial you will learn how to resolve CannotCreateTransactionException when you have multiple transaction managers in context file.

org.springframework.transaction.CannotCreateTransactionException:
 Could not open Hibernate Session for transaction;
nested exception is java.lang.IllegalStateException: Already value 
[org.springframework.jdbc.datasource.ConnectionHolder@ebac27] for key 
[org.apache.tomcat.dbcp.dbcp2.BasicDataSource@9266ae] bound to thread
 [http-nio-8080-exec-10] 

public class MyServiceImpl implements MyService {
   public void method1() {
     //code here
   }

   public void method2() {
    //code here
   }
}

This normally happens when you have multiple transaction managers in place. Let us explain it in more detail about having multiple transaction managers in applicationContext.xml  file.

First TransactionManager

<tx:annotaion-driven />

<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
 <property name="sessionFactory" ref="sessionFactory"/>
</bean>

When you define <tx:annotaion-driven />  and  @Transactional  annotation, usually it will target bean named transactionManager   i.e HibernateTransactionManager.

HibernateTransactionManager ,
 Binds a Hibernate Session from the specified factory to the thread, potentially allowing for one thread-bound Session per factory. SessionFactory.getCurrentSession() is required for Hibernate access code that needs to support this transaction handling mechanism, with the SessionFactory being configured with SpringSessionContext.
Second TransactionManager

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
 <property name="dataSource" ref="dataSource"/>
</bean>

<tx:advice id="txAdvice" transaction-manager="txManager">
 <tx:attributes>
  <tx:method name="get*" read-only="true"/>
  <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

<aop:config>
 <aop:pointcut id="serviceOperation" expression="
 execution(* com.myapp.service.MyServiceImpl.*(..)) || 
 --------------------------
 <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation"/>
</aop:config>
 
The error comes from DataSourceTransactionManager :

TransactionSynchronizationManager.bindResource(getDataSource(), connectionHolder);

i.e DataSourceTransactionManager is trying to bind the datasource (not the Hibernate Session) to the thread, but the datasource is already there and this is unexpected.

 Note that the transaction manager had not started yet to bind the  Hibernate Session  to the thread, that would happen next at  HibernateTransactionManager :

There are two possible explanations:
  • Somebody (another transaction manager?) is adding the datasource to the thread before the transaction manager and this is unexpected.
  • Or none is adding the datasource to the transaction manager, is just that at the end of the task execution none cleans the thread before returning it to the pool, maybe due an error or an unhandled exception.
If you add @Transactional annotation to your service class, then it will target bean named transactionManager in context file. i.e HibernateTransactionManager here, so that it will bind Hibernate Session to thread not datasource.

Solution

@Transactional
public class MyServiceImpl implements MyService {
   public void method1() {
     //code here
   }

   public void method2() {
    //code here
   }
}

Or without using transactional manager in  applicationContext.xml  file, use @TransactionalManager  annotation in service class.

@Transactional
@TransactionalManager("txManager")
public class MyServiceImpl implements MyService {
   public void method1() {
     //code here
   }

   public void method2() {
    //code here
   }
}

No comments:

Post a Comment