欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JPA & XA

程序员文章站 2022-03-17 11:11:14
...

After I wrote a blog about Spring & JPA settings: http://jellyfish.iteye.com/admin/blogs/899281, a friend asked me a question about XA on JPA.

 

Last time I did XA was the year 2002, so I figured maybe it's time to revisit this topic to see how things have changed since then. Well, a lot, frankly.

 

I did XA across two data sources, db2 and sybase; and across a data source and a JMS queue. I am using Spring 3 now, the first thing that I noticed is that for some unknown reason, Spring 3 drop the JOTM support (Spring 2.5 had it). Does anyone know the reason behind it?

 

So I am going to use Atomikos's libs for XA. I started from the above blog to see how much I need to change in order to get XA working.

 

First, the persistence file needs a little change (marked in red):

 

<?xml version="1.0" encoding="UTF-8" ?>

<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_2_0.xsd"
	version="2.0" xmlns="http://java.sun.com/xml/ns/persistence">

    <persistence-unit name="myPersistenceUnit" transaction-type="JTA">
        <mapping-file>my/test/dao/jpa/hibernate/book.jpa.hibernate.xml</mapping-file>
    </persistence-unit>
</persistence>

 I rename this file to persistence.jta.xml, since we need to reference this file later. Next, we need to change the Spring xml file, since the change is pretty dramatic, I just paste the entire file and then explain the beans one by one later:

<?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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <bean id="bookService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager" ref="springTransactionManager"/>
        <property name="target" ref="bookServiceTarget"/>
        <property name="transactionAttributes">
            <props>
                <prop key="*">PROPAGATION_REQUIRED, -Exception</prop>
            </props>
        </property>
    </bean>

    <bean id="userTransactionService"
          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>
            </props>
        </constructor-arg>
    </bean>

    <bean id="springTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManager">
            <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
                  init-method="init" destroy-method="close" depends-on="userTransactionService">
                <property name="forceShutdown" value="false"/>
                <property name="startupTransactionService" value="false"/>
            </bean>
        </property>
        <property name="userTransaction">
            <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp" depends-on="userTransactionService">
                <property name="transactionTimeout" value="5"/>
            </bean>
        </property>
        <property name="allowCustomIsolationLevels" value="true"/>
    </bean>

    <bean id="bookServiceTarget" class="my.test.dao.jpa.hibernate.BookService">
        <property name="dao1" ref="bookDao1"/>
        <property name="dao2" ref="bookDao2"/>
    </bean>
    
    <bean id="bookDao1" class="my.test.dao.jpa.hibernate.BookDaoJpaHibernateJtaImpl">
        <property name="entityManager">
            <bean class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
                <property name="entityManagerFactory" ref="myEmf1"/>
            </bean>
        </property>
    </bean>

    <bean id="bookDao2" class="my.test.dao.jpa.hibernate.BookDaoJpaHibernateJtaImpl">
        <property name="entityManager">
            <bean class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
                <property name="entityManagerFactory" ref="myEmf2"/>
            </bean>
        </property>
    </bean>

    <bean id="myEmf1" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="myPersistenceUnit"/>
        <property name="dataSource" ref="mydataSource1"/>
        <property name="persistenceXmlLocation" value="classpath:my/test/dao/jpa/hibernate/persistence.jta.xml"/>
        <property name="loadTimeWeaver">
            <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
        </property>
        <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>
        <property name="jpaProperties">
            <map>
                <entry key="hibernate.transaction.manager_lookup_class"
                       value="com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup"/>
            </map>
        </property>
    </bean>

    <bean id="myEmf2" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="myPersistenceUnit"/>
        <property name="dataSource" ref="mydataSource2"/>
        <property name="persistenceXmlLocation" value="classpath:my/test/dao/jpa/hibernate/persistence.jta.xml"/>
        <property name="loadTimeWeaver">
            <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
        </property>
        <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>
        <property name="jpaProperties">
            <map>
                <entry key="hibernate.transaction.manager_lookup_class"
                       value="com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup"/>
            </map>
        </property>       
    </bean>

    <bean id="mydataSource1" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
        <property name="xaDataSourceClassName" value="oracle.jdbc.xa.client.OracleXADataSource"/>
        <property name="uniqueResourceName" value="mydataSource1"/>
        <property name="poolSize" value="1"/>
        <property name="xaProperties">
            <props>
                <prop key="URL">jdbc:oracle:thin:@***</prop>
                <prop key="user">***</prop>
                <prop key="password">***</prop>
            </props>
		</property>
    </bean>

    <bean id="mydataSource2" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
        <property name="xaDataSourceClassName" value="oracle.jdbc.xa.client.OracleXADataSource"/>
        <property name="uniqueResourceName" value="mydataSource2"/>
        <property name="poolSize" value="1"/>
        <property name="xaProperties">
            <props>
                <prop key="URL">jdbc:oracle:thin:@***</prop>
                <prop key="user">***</prop>
                <prop key="password">***</prop>
            </props>
		</property>
    </bean>    
</beans>

 Let's go through this from bottom up. The last two beans are (different) data sources (I am using Oracle), make sure you use XA jdbc drivers. We want to test out the transaction management across two different databases (could be oracle/sybase/db2/sqlserver).

 

The next two beans are entity manager factor (emf). We first changed the persistence.xml new path, then add a "jpaProperties" entry according to Atomikos's document:

http://www.atomikos.com/Documentation/SpringIntegration

http://www.atomikos.com/Documentation/HibernateIntegration

 

Now the DAO class is little bit problematic since the emf was injected through @PersistenceContext. Let's remove this annotation and create a setter, like this:

public class BookDaoJpaHibernateJtaImpl  implements BookDao
{
    private EntityManager em;

    public void setEntityManager(EntityManager em)
    {
        this.em = em;
    }

    // other methods are the same
}

 So the next two DAO beans (bookDao1 and bookDao2) use this class. The emf fields are injected separately.

 

Next, we create a new class that uses the above two DAO classes:

package my.test.dao.jpa.hibernate;

import my.test.Book;
import my.test.BookDao;

public class BookService
{
    private BookDao dao1;
    private BookDao dao2;

    public void save(Book book)
    {

        dao2.save(book);
        dao1.save(book);
    }

    public void setDao2(BookDao dao2)
    {
        this.dao2 = dao2;
    }

    public void setDao1(BookDao dao1)
    {
        this.dao1 = dao1;
    }
}

 and we have the corresponding bean bookServiceTarget.

 

The next two beans are Atomikos related, together they define the transaction manager. Please check Atomikos document.

 

Finally, the first bean bookService stitch the bookServiceTarget and the transaction manager together.

 

During testing, I created two same tables in each database, add an unique index for book_id on the table in the second database. Then insert a simple book object through book service. The first run should be fine since both database tables are empty. The second run should fail because the same id

was tried to be inserted into the table in the second database. After the second run, both tables should have count 1 on rows (i.e., the second run got rolled back).