JPA & XA
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).
上一篇: 查看端口号是否被占用
推荐阅读
-
spring data jpa碰到的坑
-
使用Spring Data JPA进行数据分页与排序
-
JPA的多表复杂查询
-
spring-data-jpa实现增删改查以及分页操作方法
-
详解Spring Data JPA使用@Query注解(Using @Query)
-
Spring Data JPA使用Sort进行排序(Using Sort)
-
详解Spring Data JPA系列之投影(Projection)的用法
-
详解spring boot jpa整合QueryDSL来简化复杂操作
-
spring data jpa使用详解(推荐)
-
javaweb各种框架组合案例(六):springboot+spring data jpa(hibernate)+restful