在 Spring 批次中为 Hibernate 配置 Atomikos UserTransactionManager

Configure an Atomikos UserTransactionManager for Hibernate in Spring Batch

我需要做的是在三个不同的 Oracle 数据库上进行分布式事务。其中一个必须通过 JDBC 访问,另外两个必须通过 Hibernate 访问。这是我的 Atomikos 配置:

<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" />

Hibernate 配置的灵感来自 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>

和名为 SpringJtaPlatformAdapter 的小 class :

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();
  }

}

当我执行 运行 批处理时,我可以验证:

  1. atomikosUserTransactionatomikosTransactionManager 的 Atomikos 是最先建造的
  2. mainTransactionManager
  3. 之后立即初始化
  4. 调用了我的SpringJtaPlatformAdaptersetJtaTransactionManager方法,sTransactionManagersUserTransaction的内存地址都和之前创建的一致
  5. SpringJtaPlatformAdapterlocateTransactionManager 被调用两次(每个持久化单元一次)
  6. 然后执行我的 Hibernate 代码,我的实体被正确初始化
  7. 通过JDBC访问的数据库已更新
  8. 通过 Hibernate 访问的数据库未更新(好像发生了回滚)

在 运行 期间,日志中只出现一个警告:

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

也许这会有所帮助,我个人没有收到警告消息。

根据 Maven,我正在使用 Spring ORM 3.2.0 与 Hibernate 4.2.3 和 Atomikos 3.8.0。

我正在使用 Atomikos 4.0.0.M4 版本:

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

而不是:

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

需要注意的一点是,Hibernate 4.x moved away from TransactionManager to JtaPlatform 需要更改配置。

我对 Atomikos 提供的功能感到满意,它对我来说 运行 稳定。

一位同事发现了为什么我的休眠数据库没有更新。我的 persistence.xml 里有这个:

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

对于 Atomikos,我应该放置:

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

现在一切正常。