`
winyee
  • 浏览: 53394 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

Spring, JPA, and JTA with Hibernate and JOTM

阅读更多
http://erich.soomsam.net/2007/04/24/spring-jpa-and-jta-with-hibernate-and-jotm/

have been struggling for a couple of hours today to modify a Spring JPA configuration with a single datasource, Hibernate as the JPA provider and the JpaTransactionManager to a configuration with two XA datasources, Hibernate as the JPA provider, and the JtaTransactionManager with JOTM as the standalone JTA provider.

since the Spring and Hibernate reference manual and Javadoc documentation merely contain a number of hints on how to configure JPA with a JTA transaction manager and others are struggling as well i decided to post how i finally got it to work.

Spring, JPA, Hibernate, and JpaTransactionManager configuration
the original configuration with a single datasource, Hibernate as the JPA provider, and the JpaTransactionManager was configured as outlined in the Spring reference manual and various posts on the web. please note that the datasource configuration within the persistence.xml JPA configuration file can be moved to the Spring configuration file as shown in this getting started with JPA in Spring 2.0 post.

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
                                 http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
             version="1.0">
  <!-- transaction type set to RESOURCE_LOCAL, no JTA support -->
  <persistence-unit name="DefaultPersistenceUnit" transaction-type="RESOURCE_LOCAL">
    <properties>
      <property name="hibernate.archive.autodetection" value="class, hbm"/>
      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.format_sql" value="true"/>
      <property name="hibernate.connection.driver_class" value="oracle.jdbc.driver.OracleDriver"/>
      <property name="hibernate.connection.url" value="jdbc:oracle:thin:@HOST:PORT:SID"/>
      <property name="hibernate.dialect" value="org.hibernate.dialect.OracleDialect"/>
      <property name="hibernate.connection.username" value="USERNAME"/>
      <property name="hibernate.connection.password" value="PASSWORD"/>
    </properties>
  </persistence-unit>
</persistence>


spring-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
                           http://www.springframework.org/schema/tx
                           http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
 
  <!-- enables interpretation of the @Required annotation to ensure that dependency injection actually occures -->
  <bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/>  
 
  <!-- enables interpretation of the @PersistenceUnit/@PersistenceContext annotations providing convenient
       access to EntityManagerFactory/EntityManager -->
  <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
 
  <!-- uses the persistence unit defined in the META-INF/persistence.xml JPA configuration file -->
  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean"/>
 
  <!-- transaction manager for use with a single JPA EntityManagerFactory for transactional data access
       to a single datasource -->
  <bean id="jpaTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
      <property name="entityManagerFactory" ref="entityManagerFactory"/>
  </bean>
 
  <!-- enables interpretation of the @Transactional annotation for declerative transaction managment
       using the specified JpaTransactionManager -->
  <tx:annotation-driven transaction-manager="jpaTransactionManager" proxy-target-class="false"/>
 
  <!-- enables interpretation of the @Configurable annotation for domain object dependency injection -->
  <aop:spring-configured/>
</beans>


Spring, JPA, Hibernate, JtaTransactionManager, and JOTM configuration
defining the XA datasources for use with the JOTM transaction manager is well documented. merely the fact that the JtaTransactionManager autodetects most JTA transaction managers but not the standalone JOTM transaction manager as documented in the JtaTransactionManager Javadoc was a bit tricky. a static accessor method is required which is the reason why the JotmFactoryBean has to be used. the object returned by the JotmFactoryBean implements both the UserTransaction and TransactionManager interface which is detected by the JtaTransactionManager implementation. it is, therefore, sufficient to supply the JotmFactoryBean merely to the userTransaction property of the JtaTransactionManager and not necessary to specify another reference to the transactionManager property.

once i had the XA datasources and the JtaTransactionManager in place the tricky part was to replace the JpaTransactionManager with the newly defined JtaTransactionManager and to configure the Hibernate JPA provider accordingly to make it participate in JTA transactions. Spring offers two ways to setup the JPA EntityManagerFactory. i switched the configuration to the more powerful LocalContainerEntityManagerFactoryBean and moved the properties previously defined in the persistence.xml JPA configuration file to the Spring configuration. furthermore, i replaced the datasource definition with a reference to the XA datasource and implemented a custom PersistenceUnitPostProcessor to be able to specify the JTA capable datasource directly within the Spring configuration file. in addition, setting the jpaPropertyMap property of the LocalContainerEntityManagerFactoryBean correctly to tell Hibernate to use the JOTM JTA transaction manager is necessary.

please note that a number of optional configuration properties can be supplied to Hibernate via the jpaPropertyMap. make sure to read and understand the sections on transaction strategy configuration, current session context management with JTA, and contextual sessions and set the corresponding configuration properties according to your needs.

in our current scenario we require JPA access to one of the XA datasources while JDBC is used to access the other datasource. that's the reason why no entity manager factory is defined for the second datasource. it should, however, be possible to adapt the configuration shown below and configure entity manager factories for both XA datasources.

persistence.xml
 <?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
                                 http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
             version="1.0">
  <!-- transaction type set to JTA, transaction suspension and XA supported -->
  <persistence-unit name="SpringConfiguredPersistenceUnit" transaction-type="JTA"/>
</persistence>spring-context.xml 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
                           http://www.springframework.org/schema/tx
                           http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
 
  <!-- enables interpretation of the @Required annotation to ensure that dependency injection actually occures -->
  <bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/>  
 
  <!-- enables interpretation of the @PersistenceUnit/@PersistenceContext annotations providing convenient
       access to EntityManagerFactory/EntityManager -->
  <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
 
  <!-- first XA data source -->
  <bean id="bdbInnerDataSource" class="org.enhydra.jdbc.standard.StandardXADataSource">
    <property name="transactionManager" ref="jotm"/>
    <property name="driverName" value="oracle.jdbc.driver.OracleDriver"/>
    <property name="url" value="jdbc:oracle:thin:@HOST:PORT:SID"/>
    <property name="user" value="USERNAME"/>
    <property name="password" value="PASSWORD"/>
  </bean>
 
  <!-- first XA data source pool -->
  <bean id="bdbDataSource" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource">
    <property name="transactionManager" ref="jotm"/>
    <property name="dataSource" ref="bdbInnerDataSource"/>
    <property name="user" value="USERNAME"/>
    <property name="password" value="PASSWORD"/>
    <property name="maxSize" value="4"/>
  </bean>
 
  <!-- second XA data source -->
  <bean id="bpeInnerDataSource" class="org.enhydra.jdbc.standard.StandardXADataSource">
    <property name="transactionManager" ref="jotm"/>
    <property name="driverName" value="oracle.jdbc.driver.OracleDriver"/>
    <property name="url" value="jdbc:oracle:thin:@HOST:PORT:SID"/>
    <property name="user" value="USERNAME"/>
    <property name="password" value="PASSWORD"/>
  </bean>
 
  <!-- second XA data source pool -->
  <bean id="bpeDataSource" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource">
    <property name="transactionManager" ref="jotm"/>
    <property name="dataSource" ref="bpeInnerDataSource"/>
    <property name="user" value="USERNAME"/>
    <property name="password" value="PASSWORD"/>
    <property name="maxSize" value="4"/>
  </bean>
 
  <!-- required cos the standalone JOTM transaction manager is not autodetected by the
       JtaTransactionManager cos it requires a static accessor method -->
  <bean id="jotm" class="org.springframework.transaction.jta.JotmFactoryBean"/>
 
  <!-- supplying the JotmFactoryBean merely to the userTransaction property cos JtaTransactionManager
       autodetects that the object returned by the JotmFactoryBean implements both the
       UserTransaction and the TransactionManager interface -->
  <bean id="jtaTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="userTransaction" ref="jotm"/>
    <property name="allowCustomIsolationLevels" value="true"/>
  </bean>  
 
  <!-- settings previously specified in the persistence.xml JPA configuration file are now defined with the
       LocalContainerEntityManagerFactoryBean configuration -->  
  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">    
    <!-- reference to the XA datasource -->
    <property name="dataSource" ref="bdbDataSource"/>
    <!-- specify Hibernate as the the JPA provider -->
    <property name="jpaVendorAdapter">
      <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        <property name="showSql" value="true"/>
        <property name="generateDdl" value="false"/>
        <property name="database" value="ORACLE"/>
        <property name="databasePlatform" value="org.hibernate.dialect.OracleDialect"/>
      </bean>
    </property>
    <!-- configure Hibernate to participate in JTA transactions using the JOTM transaction manager and
         specify further Hibernate specific configuration properties -->
    <property name="jpaPropertyMap">
      <map>
        <entry key="hibernate.transaction.manager_lookup_class"
               value="org.hibernate.transaction.JOTMTransactionManagerLookup"/>
        <entry key="hibernate.transaction.flush_before_completion"
               value="true"/>
        <entry key="hibernate.transaction.auto_close_session"
               value="true"/>
        <entry key="hibernate.current_session_context_class"
               value="jta"/>
        <entry key="hibernate.connection.release_mode"
               value="auto"/>                            
      </map>
    </property>
    <!-- specify that the Hibernate JPA dialect should be used, probably not necessary since 
         HibernateJpaVendorAdapter will most likely set this property -->
    <property name="jpaDialect">
      <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
    </property>
    <!-- custom implementation to enrich the PersistenceUnitInfo read from the persistence.xml
         JPA configuration file with the JTA datasource. specifying the JTA datasource directly in
         the Spring configuration file has the advantage that we can use a direct reference to the
         datasource instead of using a JNDI name as requied by the jta-data-source setting in the
         persistence.xml file -->
    <property name="persistenceUnitPostProcessors">
      <bean class="sample.JtaPersistenceUnitPostProcessor">
        <property name="jtaDataSource" ref="bdbDataSource"/>		
      </bean>
    </property>
  </bean>
 
  <!-- enables interpretation of the @Transactional annotation for declerative transaction managment
       using the specified JtaTransactionManager -->
  <tx:annotation-driven transaction-manager="jtaTransactionManager" proxy-target-class="false"/>
 
  <!-- enables interpretation of the @Configurable annotation for domain object dependency injection -->
  <aop:spring-configured/>
</beans>


  sample.JtaPersistenceUnitPostProcessor.java
package sample;
 
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo;
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor;
 
/**
 * the {@link JtaPersistenceUnitPostProcessor} enables us to define the JTA capable datasource
 * which should be used by the JPA provider directly as a reference within the Spring configuration
 * file.
 */
public class JtaPersistenceUnitPostProcessor implements PersistenceUnitPostProcessor {
  /**
   * a reference to the JTA capable datasource which is add to the PersistenceUnitInfo during post
   * processing instead of beeing specified using the "jta-data-source" setting in the persistence.xml
   * configuration file
   */
  private DataSource jtaDataSource;
 
  /**
   * enrich the PersistenceUnitInfo read from the persistence.xml configuration file with a reference
   * to the jtaDataSource injected via the Spring configuration. the JTA capable datasource is then
   * used by the LocalContainerEntityManagerFactoryBean to create the EntityManagerFactory
   *
   * @see PersistenceUnitPostProcessor#postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo)
   */
  public void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo mutablePersistenceUnitInfo) {
    mutablePersistenceUnitInfo.setJtaDataSource(getJtaDataSource());
  }
 
  /**
   * getter for jtaDataSource
   * 
   * @return the JTA capable datasource supplied via the setter
   */
  public DataSource getJtaDataSource() {
    return jtaDataSource;
  }
 
  /**
   * setter for jtaDataSource 
   *
   * @param jtaDataSource the JTA capable datasource added to the PersistenceUnitInfo during post processing
   */
  @Required
  public void setJtaDataSource(DataSource jtaDataSource) {
    this.jtaDataSource = jtaDataSource;
  }
}


if you don't want to implement and use a custom PersistenceUnitPostProcessor you can add the jta-data-source setting to the persistence-unit configuration in the persistence.xml file. add the following snippet and specify the JNDI name to the JTA capable datasource:
 <jta-data-source>jdbc/bdbDataSource</jta-data-source>

you will then have to expose the JTA datasource via JNDI which can be achieved by using the XBean library and adding the following snippet to the spring-context.xml file:

<bean id="jndiContext" class="org.apache.xbean.spring.jndi.SpringInitialContextFactory"
      factory-method="makeInitialContext" scope="singleton" lazy-init="false">                
  <property name="entries">
    <map>
      <entry key="jdbc/bdbDataSource" value-ref="bdbDataSource"/>
    </map>
  </property>
</bean>
 


if you plan to use a JTA transaction manager other than the standalone JOTM transaction manager you will have to adapt the XA datasource configuration, most likely use the autodetect mode of the JtaTransactionManager by removing the JotmFactoryBean (or specifying another factory bean), and define a different hibernate transaction manager lookup class. for in container deployment you will most likely want to use the JndiObjectFactoryBean to integrate the datasources already configured within the application server into the Spring application context (by simply specifying the JNDI name).
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics