理解分布式事务JTA原理参见:http://www.ibm.com/developerworks/cn/java/j-lo-jta/
JTA实现产品介绍:http://blog.chinaunix.net/uid-122937-id-3793220.html
Atomikos官网无法访问,不过Maven中央库中具atomikos包。Atomikos集成Spring,Hibernate,Mybatis网上文章比较多,本文是通过JavaSE的方式借用Spring配置来测试Atomikos对JTA的实现。
下面做一件事,就是两(+)个数据库,在一个事务里对其分别对数据库操作验证操作的原子性,即要么两个数据库的操作都成功,要么都失败。
1.准备工作
1.1 Maven pom.xml中添加依赖包
atomikos:(目前最新版)
<dependency> <groupId>com.atomikos</groupId> <artifactId>transactions-jdbc</artifactId> <version>3.9.3</version> </dependency>
jar依赖图:
Postgresql数据库驱动:
<dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>9.2-1004-jdbc4</version> </dependency>
注:文中使用的是Postgresql V9.2
javax.transaction.transaction-api.1.1:
<!-- Include in javaee-api --> <!-- <dependency> <groupId>javax.transaction</groupId> <artifactId>transaction-api</artifactId> <version>1.1</version> </dependency> -->
<!-- JavaEE --> <!-- javaee-api包含了JavaEE规范中的api,如servlet-api,persistence-api, transaction-api等 --> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> </dependency>
注:根据需要选择其一即可。
Spring,Junit依赖这里省略。
1.2 创建数据库以及表
数据库分别是:javaee,tomdb
2.在项目中添加配置文件
spring-jta.xml 和transaction.properties(模版见文中附件)文件,spring-jta.xml在src/main/resources/integration下,transaction.properties在src/main/resources/下。
2.1在spring-jta.xml中配置两个XADataSource:
<!-- 使用分布式事务时设置Postgresql的max_prepared_transactions为大于0的值,该值默认是0 --> <!-- 数据库A --> <bean id="a" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName" value="pg/a" /> <property name="xaDataSourceClassName" value="org.postgresql.xa.PGXADataSource" /> <property name="xaProperties"> <props> <prop key="user">postgres</prop> <prop key="password">postgres</prop> <prop key="serverName">localhost</prop> <prop key="portNumber">5432</prop> <prop key="databaseName">tomdb</prop> </props> </property> <property name="poolSize" value="10" /> <property name="reapTimeout" value="20000" /> </bean> <bean id="b" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName" value="pg/b" /> <property name="xaDataSourceClassName" value="org.postgresql.xa.PGXADataSource" /> <property name="xaProperties"> <props> <prop key="user">postgres</prop> <prop key="password">postgres</prop> <prop key="serverName">localhost</prop> <prop key="portNumber">5432</prop> <prop key="databaseName">javaee</prop> </props> </property> <property name="poolSize" value="10" /> <property name="reapTimeout" value="20000" /> </bean>
说明:
Postgresql的max_prepared_transactions参数值默认是0,要开启XA需要设置该值至少和max_connections参数值一样大,该参数在PostgreSQL\9.3\data\postgresql.conf文件中。
PGXADataSource的父类BaseDataSource没有url属性,可需要分别设置serverName,portNumber,databaseName等属性。不同的数据库驱动有不同的实现方法。
2.2 配置事务管理对象和UserTransaction接口实现
<!-- atomikos事务管理 --> <bean id="atomikosUserTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close"> <description>UserTransactionManager</description> <property name="forceShutdown" value="true" /> </bean> <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp"> <property name="transactionTimeout" value="300" /> </bean> <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManager" ref="atomikosUserTransactionManager"></property> </bean>
上面三个Bean可以独立使用来进行事务控制,具体看下面3。
3. 编写测试
3.1 使用atomikosUserTransactionManager对象测试(TestAtomikos1.java)
package secondriver.springsubway.example.jta; import java.sql.Connection; import java.sql.SQLException; import javax.transaction.HeuristicMixedException; import javax.transaction.HeuristicRollbackException; import javax.transaction.NotSupportedException; import javax.transaction.RollbackException; import javax.transaction.SystemException; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.atomikos.icatch.jta.UserTransactionManager; import com.atomikos.jdbc.AtomikosDataSourceBean; public class TestAtomikos1 { public static ApplicationContext ctx; @BeforeClass public static void beforeClass() { ctx = new ClassPathXmlApplicationContext( "classpath:integration/spring-jta.xml"); } public static void afterClass() { ctx = null; } @Test public void test1() { exe("abc", "abc"); } @Test public void test2() { exe("123=", "123"); } public void exe(String av, String bv) { AtomikosDataSourceBean adsA = (AtomikosDataSourceBean) ctx.getBean("a"); AtomikosDataSourceBean adsB = (AtomikosDataSourceBean) ctx.getBean("b"); Connection connA; Connection connB; UserTransactionManager utm = (UserTransactionManager) ctx .getBean("atomikosUserTransactionManager"); try { utm.begin(); connA = adsA.getConnection(); connB = adsB.getConnection(); connA.prepareStatement( "insert into jta_temp (value) values('" + av + "')") .execute(); connB.prepareStatement( "insert into jta_temp (value) values('" + bv + "')") .execute(); utm.commit(); } catch (SQLException | NotSupportedException | SystemException | SecurityException | IllegalStateException | RollbackException | HeuristicMixedException | HeuristicRollbackException e) { e.printStackTrace(); try { utm.rollback(); } catch (IllegalStateException | SecurityException | SystemException e1) { e1.printStackTrace(); } } } }
3.2使用Spring的JtaUserTransactionManager对象测试(TestAtomikos2.java 修改TestAtomikos1.java中的exe方法即可)
@Test public void test1() { exe("abc", "abc"); } @Test public void test2() { exe("123=", "123"); }
public void exe(String av, String bv) { TransactionFactory txm = (TransactionFactory) ctx .getBean("transactionManager"); JdbcTemplate a = (JdbcTemplate) ctx.getBean("jdbcTemplateA"); JdbcTemplate b = (JdbcTemplate) ctx.getBean("jdbcTemplateB"); Transaction tx =null; try { tx = txm.createTransaction("tx-name-define", 10000); a.update("insert into jta_temp (value) values('" + av + "')"); b.update("insert into jta_temp (value) values('" + bv + "')"); tx.commit(); } catch (NotSupportedException | SystemException | SecurityException | RollbackException | HeuristicMixedException | HeuristicRollbackException e) { e.printStackTrace(); if(tx!=null){ try { tx.rollback(); } catch (IllegalStateException | SystemException e1) { e1.printStackTrace(); } } } }
3.3使用atomikosUserTransaction Bean对象进行测试(TestAtomikos3.java 修改TestAtomikos1.java中的exe方法即可)
@Test public void test1() { exe("abc", "abc"); } @Test public void test2() { exe("123", "123="); }
public void exe(String av, String bv) { AtomikosDataSourceBean adsA = (AtomikosDataSourceBean) ctx.getBean("a"); AtomikosDataSourceBean adsB = (AtomikosDataSourceBean) ctx.getBean("b"); Connection connA; Connection connB; UserTransaction utx = (UserTransaction) ctx .getBean("atomikosUserTransaction"); try { utx.begin(); connA = adsA.getConnection(); connB = adsB.getConnection(); connA.prepareStatement( "insert into jta_temp (value) values('" + av + "')") .execute(); connB.prepareStatement( "insert into jta_temp (value) values('" + bv + "')") .execute(); utx.commit(); } catch (SQLException | NotSupportedException | SystemException | SecurityException | IllegalStateException | RollbackException | HeuristicMixedException | HeuristicRollbackException e) { e.printStackTrace(); try { utx.rollback(); } catch (IllegalStateException | SecurityException | SystemException e1) { e1.printStackTrace(); } } }
使用上述三种UserTransaction进行测试,其中test1方法是成功执行,test2方法是执行失败的(因为插入的值长度超过的字段长度限制)。通过分析之后,如果分布式事物控制正确,那么数据库中写入的值对于两张不同的表而言,是没有数字值被写入的。如图所示:
在测试过程中,经过对比确实达到了分布式事务控制的效果。
整个操作的架构图如下(摘自Atomikos Blog)
关于JTA原理文章开始提到的那篇文章,写的很详细和清晰,可以细细阅读和理解。