Wis
Wis

Reputation: 725

Configure an Atomikos UserTransactionManager for Hibernate in Spring Batch

What I need to do is a distributed transaction over three distinct Oracle databases. One of each must be accessed through JDBC, the two others through Hibernate. Here is my Atomikos configuration :

<bean id="mainDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean"
    init-method="init" destroy-method="close">
    <property name="xaDataSourceClassName" value="${mainDataSource.jdbc.className}" />
    <property name="uniqueResourceName" value="${mainDataSource.jdbc.uniqueName}" />
    <property name="poolSize" value="${mainDataSource.jdbc.maxPoolSize}" />
    <property name="testQuery" value="${mainDataSource.jdbc.testQuery}" />
    <property name="xaProperties">
        <props>
            <prop key="URL">${mainDataSource.jdbc.url}</prop>
            <prop key="user">${mainDataSource.jdbc.user}</prop>
            <prop key="password">${mainDataSource.jdbc.password}</prop>
        </props>
    </property>
</bean>

<bean id="optionalDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean"
    init-method="init" destroy-method="close">
    <property name="xaDataSourceClassName" value="${optionalDataSource.jdbc.className}" />
    <property name="uniqueResourceName" value="${optionalDataSource.jdbc.uniqueName}" />
    <property name="poolSize" value="${optionalDataSource.jdbc.maxPoolSize}" />
    <property name="testQuery" value="${optionalDataSource.jdbc.testQuery}" />
    <property name="xaProperties">
        <props>
            <prop key="URL">${optionalDataSource.jdbc.url}</prop>
            <prop key="user">${optionalDataSource.jdbc.user}</prop>
            <prop key="password">${optionalDataSource.jdbc.password}</prop>
        </props>
    </property>
</bean>

<bean id="eventDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean"
    init-method="init" destroy-method="close">
    <property name="xaDataSourceClassName" value="${eventDataSource.jdbc.className}" />
    <property name="uniqueResourceName" value="${eventDataSource.jdbc.uniqueName}" />
    <property name="poolSize" value="${eventDataSource.jdbc.maxPoolSize}" />
    <property name="testQuery" value="${eventDataSource.jdbc.testQuery}" />
    <property name="xaProperties">
        <props>
            <prop key="URL">${eventDataSource.jdbc.url}</prop>
            <prop key="user">${eventDataSource.jdbc.user}</prop>
            <prop key="password">${eventDataSource.jdbc.password}</prop>
        </props>
    </property>
</bean>

<bean id="atomikosTransactionService" class="com.atomikos.icatch.config.UserTransactionServiceImp"
    init-method="init" destroy-method="shutdownForce">
    <constructor-arg>
        <props>
            <prop key="com.atomikos.icatch.service">com.atomikos.icatch.standalone.UserTransactionServiceFactory
            </prop>
            <prop key="com.atomikos.icatch.tm_unique_name">${transactionmanager.atomikos.tmId}</prop>
            <prop key="com.atomikos.icatch.enable_logging">${transactionmanager.atomikos.enablelogging}</prop>
            <prop key="com.atomikos.icatch.output_dir">${transactionmanager.atomikos.console}</prop>
            <prop key="com.atomikos.icatch.log_base_dir">${transactionmanager.atomikos.tmLog}</prop>
            <prop key="com.atomikos.icatch.log_base_name">${transactionmanager.atomikos.tmLogBaseName}</prop>
        </props>
    </constructor-arg>
</bean>

<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp"
    depends-on="atomikosTransactionService">
    <property name="transactionTimeout" value="300" />
</bean>

<bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
    init-method="init" depends-on="atomikosTransactionService"
    destroy-method="close">
    <!-- when close is called, should we force transactions to terminate or 
        not? -->
    <property name="forceShutdown" value="true" />
    <property name="startupTransactionService" value="false" />
</bean>

<bean id="mainTransactionManager"
    class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="transactionManager" ref="atomikosTransactionManager" />
    <property name="userTransaction" ref="atomikosUserTransaction" />
</bean>

<!-- Der mainTransactionManager ist der Default-TransactionManager von Spring. -->
<alias name="mainTransactionManager" alias="transactionManager" />

The Hibernate configuration is inspired by the solution found on this topic :

<!-- inject the Atomikos transaction manager into a Spring Hibernate adapter 
    for JTA Platform -->
<bean id="springJtaPlatformAdapter"
    class="my.domain.spring.hibernate.jta.SpringJtaPlatformAdapter">
    <!-- the mainTransactionManager is defined in ora_jtam_atomikos.xml imported -->
    <property name="jtaTransactionManager" ref="mainTransactionManager" />
</bean>

<bean id="entityManagerFactoryEVL"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
    depends-on="mainTransactionManager,springJtaPlatformAdapter">
    <property name="persistenceXmlLocation" value="classpath:evl_persistence.xml" />
    <property name="persistenceUnitName" value="evlPersistenceUnit" />
    <property name="dataSource" ref="optionalDataSource" />
    <property name="loadTimeWeaver">
        <bean
            class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
    </property>
    <property name="jpaPropertyMap" ref="jpaPropertyMapEVL"></property>
</bean>

<util:map id="jpaPropertyMapEVL">
    <entry key="hibernate.hbm2ddl.auto" value="validate" />
    <entry key="hibernate.show_sql" value="false" />
    <entry key="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect" />
    <entry key="hibernate.transaction.jta.platform"
        value="my.domain.spring.hibernate.jta.SpringJtaPlatformAdapter" />
</util:map>

<bean id="entityManagerFactoryVVL"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
    depends-on="mainTransactionManager,springJtaPlatformAdapter">
    <property name="persistenceXmlLocation" value="classpath:vvl_persistence.xml" />
    <property name="persistenceUnitName" value="vvlPersistenceUnit" />
    <property name="dataSource" ref="eventDataSource" />
    <property name="loadTimeWeaver">
        <bean
            class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
    </property>
    <property name="jpaPropertyMap" ref="jpaPropertyMapVVL"></property>
</bean>

<util:map id="jpaPropertyMapVVL">
    <entry key="hibernate.hbm2ddl.auto" value="validate" />
    <entry key="hibernate.show_sql" value="false" />
    <entry key="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect" />
    <entry key="hibernate.transaction.jta.platform"
        value="my.domain.spring.hibernate.jta.SpringJtaPlatformAdapter" />
</util:map>

And the small class named SpringJtaPlatformAdapter :

public class SpringJtaPlatformAdapter extends AbstractJtaPlatform {
  private static final long serialVersionUID = -7030175748923257913L;
  private static TransactionManager sTransactionManager;
  private static UserTransaction sUserTransaction;

  @Override
  protected TransactionManager locateTransactionManager() {
    Assert.notNull(sTransactionManager, "TransactionManager has not been setted");
    return sTransactionManager;
  }

  @Override
  protected UserTransaction locateUserTransaction() {
    Assert.notNull(sUserTransaction, "UserTransaction has not been setted");
    return sUserTransaction;
  }

  public void setJtaTransactionManager(JtaTransactionManager jtaTransactionManager) {
    sTransactionManager = jtaTransactionManager.getTransactionManager();
    sUserTransaction = jtaTransactionManager.getUserTransaction();
  }

}

When I do run the batch, I could verified that :

  1. the atomikosUserTransaction and atomikosTransactionManager of Atomikos are constructed first
  2. the mainTransactionManager is initialized right after
  3. the setJtaTransactionManager method of my SpringJtaPlatformAdapter is called, both memory addresses for the sTransactionManager and sUserTransaction are consistent with the ones created before
  4. the locateTransactionManager of the SpringJtaPlatformAdapter is called twice (one for each persistence unit)
  5. my Hibernate code is then performed, my entities are correctly initialized
  6. the database which is accessed through JDBC is updated
  7. the databases which are accessed through Hibernate are NOT updated (as if a rollback took place)

During the run, only one warning appears in the logs :

WARN main SessionFactoryImpl:1530 - HHH000008: JTASessionContext being used with JDBCTransactionFactory; auto-flush will not operate correctly with getCurrentSession()

Maybe that can help, I personnaly don't get the warning message.

According to Maven, I'm using Spring ORM 3.2.0 with Hibernate 4.2.3 and Atomikos 3.8.0.

Upvotes: 2

Views: 4696

Answers (2)

manish
manish

Reputation: 20135

I am using Atomikos 4.0.0.M4 release with:

<entry key="hibernate.transaction.jta.platform"
  value="com.atomikos.icatch.jta.hibernate4.AtomikosPlatform"/>

instead of:

<entry key="hibernate.transaction.manager_lookup_class"
  value="com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup" />

The point to note is that Hibernate 4.x moved away from TransactionManager to JtaPlatform which has necessitated the need for the change in configuration.

I am happy with the functionality provided by Atomikos and it has been running stably for me.

Upvotes: 3

Wis
Wis

Reputation: 725

One colleague found why my hibernate databases were not updated. I had this in my persistence.xml :

<persistence-unit name="evlPersistenceUnit" transaction-type="RESOUCE_LOCAL">

For Atomikos, I should have placed :

<persistence-unit name="evlPersistenceUnit" transaction-type="JTA">

Now it's working just fine.

Upvotes: 0

Related Questions