2

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.

Community
  • 1
  • 1
Wis
  • 705
  • 1
  • 11
  • 34

2 Answers2

3

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.

manish
  • 19,695
  • 5
  • 67
  • 91
  • It did solve the exception. Now my batch is completing. But the Hibernate database changes are not persisted. It's as if it did a rollback for Hibernate while commiting JDBC queries... – Wis Feb 27 '15 at 13:30
  • That is difficult to predict from just the configuration. You will have to debug your app to find out what's preventing Hibernate from persisting to the database. – manish Feb 27 '15 at 13:56
  • I'm pretty sure the `UserTransaction` I did configure is not accessed by Hibernate. Instead, I suspect the `TransactionManagerLookup` do create a fresh `UserTransaction`, which is shared by the two Hibernate entity managers. The spring commit is performed on the transaction identified by `id="atomikosUserTransaction"`, and consequently not performed for Hibernate. I'll try to go with debug deep into the libraries to watch the memory address of the two transactions. But that may be uneasy... – Wis Mar 02 '15 at 07:37
  • Verified through debug that only a single `UserTransactionManager` is created. I updated my question to reflect the latest changes I made to my configuration. – Wis Mar 02 '15 at 10:14
0

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.

Wis
  • 705
  • 1
  • 11
  • 34