Thursday, October 14, 2010

How to use 2 or more data sources in Hibernate along with Spring's Declarative Transaction Management?

You may quickly response "just use Spirng's JtaTransactionManager".
But wait. Before deciding to use JTA, you should make sure that local transaction really doesn't meet your requirement because JTA requires many more resources and is much slower than local transactions.
Even you have 2 or more data sources, you don't need to use use JTA in the following cases:
  • No business method has to access more than 1 data source;
  • Event your business method has to access more than 1 data source, you can still use a technique similar to “Last Resource Commit Optimization" with local transactions if you can tolerate occasional data inconsistency. 
Here is the example in my "Revving up Your Hibernate Engine": 

Our application has several service layer methods which only deal with database “A” in most instances; however occasionally they also retrieve read-only data from database “B”. Because database “B” only provides read-only data, we still use local transactions on both databases for those methods.
The service layer does have one method involving data changes on both databases. Here is the pseudo-code:
//Make sure a local transaction on database A exists
@Transactional (readOnly=false, propagation=Propagation.REQUIRED)
public void saveIsoBids() {
  //it participates in the above annotated local transaction
  insertBidsInDatabaseA();
  //it runs in its own local transaction on database B
  insertBidRequestsInDatabaseB(); //must be the last operation

Because insertBidRequestsInDatabaseB() is the last operation in saveIsoBids (), only the following scenario can cause data inconsistency:
The local transaction on database “A” fails to commit when the execution returns from saveIsoBids ().
However even if you use JTA for saveIsoBids (), you still get data inconsistency when the second commit phase fails in the two phase commit (2PC) process. So if you can deal with the above data inconsistency and really don’t want JTA complexities for just one or a few methods, you should use local transactions. 

Now suppose you will use local transaction, i.e.Spring's HibernateTransactionManager. In your context XML, you define the needed transaction manager bean:
  <tx:annotation-driven transaction-manager="txManager1"/>
  <bean id="txManager1"
 class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory1"/>
  </bean>

  <bean id="sessionFactory1"
 class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="dataSource1"/>
  </bean>

The above XML just configured one data source and its related Hibernate session factory and transaction manager.
The above annotation-driven element only allows you to specify one transaction manager (it should do this way otherwise which transaction manager will the annotated service method use?) and it is global.
So what happens if you specify another transaction manager in a different context XML:
  <tx:annotation-driven transaction-manager="txManager2"/>
  <bean id="txManager1"

Based on my testing, the result is unexpected.

The solution is you can only use the transaction manager along with Spring's declarative transaction; other transaction managers must use Spring's XML configuration:

<tx:advice id="txAdvice" transaction-manager="txManager2">
  <tx:attributes>
    <!-- all other methods starting with 'get' are read-only -->
    <tx:method name="get*" read-only="true"/>

    <!-- other methods use the default transaction settings (see below) -->
    <tx:method name="*"/>
  </tx:attributes>
</tx:advice>

<bean id="txManager2"
 class="org.springframework.orm.hibernate3.HibernateTransactionManager">
  <property name="sessionFactory" ref="sessionFactory2"/>
</bean>

<aop:config>
  <aop:pointcut id="serviceOperation" expression="...ignored"/>
  <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation"/>
</aop:config>

1 comment: