blog迁移至 :http://www.micmiu.com
在2011.7.1号这个特殊的日子,发一blog,名为:分布式事务JTA之实践:Spring+ATOMIKOS 来庆祝党的90岁生日和iteye博客产品成功改版上线。
本文的目录结构如下:
一、概述:
本文主要讲述如何基于Atomikos 和 spring 在项目中实现分布式事务管理
二、应用场景:
如果项目中的数据源来自多个数据库,同时又需要在多数据源中保证事务,此时就需要用到分布式事务处理了。
三、实验模拟需求 :
比如有两个对象:用户信息、用户存款,用户信息存在数据库A、存款信息存在数据库B,若客户甲向乙转账,需要在数据库B中对甲、乙的存款信息修改,同时在数据库A中把甲、乙的备注信息最新为最近一次的操作时间。
四 、实例测试环境 :
说明:
1. 测试的数据库需要支持分布式事务,同时JDBC要支持XA 连接驱动 。本次测试用的mysql5.1是支持事务的,JDBC驱动版本:mysql-connector-java-5.1.7-bin.jar,包含对 XA连接的支持:com.mysql.jdbc.jdbc2.optional.MysqlXAConnection。
2. 附件提供AtomikosTransactionsEssentials 3.7.0 lib包下载 :AtomikosTransactionsEssentials-3.7.0-lib.zip
官方下载地址:http://www.atomikos.com/Main/TransactionsEssentialsDownloadForm ,需要先注册才能下载。同时这里也提供目前3.7.0的下载链接:http://www.atomikos.com/downloads/transactions-essentials/com/atomikos/AtomikosTransactionsEssentials/3.7.0/AtomikosTransactionsEssentials-3.7.0-bin.zip
五、代码及配置介绍:
源代码下载 :分布式事务实例演示源代码
1.代码的目录结构图如下:
转账测试的的代码片段:
/** * 转账测试 * @param srcId * @param destId * @param money * @return boolean */ public boolean doTestTransfer(String srcId, String destId, float money) { BankAccount srcAccount = bankAccountDao.getByUserName(srcId); BankAccount destAccount = bankAccountDao.getByUserName(destId); if (srcAccount.getDeposit() < money) { System.out.println("warn :" + srcAccount.getUserName() + " has not enough money to transfer"); return false; } srcAccount.setDeposit(srcAccount.getDeposit() - money); destAccount.setDeposit(destAccount.getDeposit() + money); // 把更新存款信息置于异常发生之前 bankAccountDao.update(srcAccount); bankAccountDao.update(destAccount); Date curTime = new Date(); UserInfo srcUser = userInfoDao.getById(srcId); UserInfo destUser = userInfoDao.getById(destId); destUser.setRemark1(curTime + ""); destUser.setRemark2(curTime + ""); // 把更新基本信息置于异常发生之前 userInfoDao.update(destUser); srcUser.setRemark1(curTime + ""); if (srcAccount.getDeposit() < 18000) { throw new RuntimeException("michael test exception for JTA "); } srcUser.setRemark2(curTime + ""); userInfoDao.update(srcUser); System.out.println("success done:" + srcAccount.getUserName() + " transfer ¥" + money + " to " + destAccount.getUserName()); return true; }
2. 配置文件详细介绍:
jta.jdbc.properties
#see http://sjsky.iteye.com # eg. for mysql jdbc.SDS.class=com.mysql.jdbc.jdbc2.optional.MysqlXADataSource jdbc.SDS.properties=URL=jdbc:mysql://192.168.8.253:3306/demota;user=root;password=111111 jdbc.SDS2.class=com.mysql.jdbc.jdbc2.optional.MysqlXADataSource jdbc.SDS2.properties=URL=jdbc:mysql://192.168.8.150:3306/demota;user=root;password=111111
jta.properties
#see http://sjsky.iteye.com com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory com.atomikos.icatch.console_file_name = tm.out com.atomikos.icatch.log_base_name = tmlog com.atomikos.icatch.tm_unique_name = com.atomikos.spring.jdbc.tm com.atomikos.icatch.console_log_level = INFO
jta1.hibernate.cfg.xml
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="dialect"> org.hibernate.dialect.MySQL5Dialect </property> <property name="hbm2ddl.auto">update</property> <mapping class="michael.jta.atomikos.domain.UserInfo" /> </session-factory> </hibernate-configuration>
jta2.hibernate.cfg.xml
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="show_sql">true</property> <property name="dialect"> org.hibernate.dialect.MySQL5Dialect </property> <property name="hbm2ddl.auto">update</property> <mapping class="michael.jta.atomikos.domain.BankAccount" /> </session-factory> </hibernate-configuration>
jta.spring.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <!-- Configurer that replaces ${...} placeholders with values from properties files --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:jta.jdbc.properties</value> </list> </property> </bean> <!-- 数据源配置 http://sjsky.iteye.com--> <bean id="SDS" class="com.atomikos.jdbc.SimpleDataSourceBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName"> <value>SDS</value> </property> <property name="xaDataSourceClassName"> <value>${jdbc.SDS.class}</value> </property> <property name="xaDataSourceProperties"> <value>${jdbc.SDS.properties}</value> </property> <property name="exclusiveConnectionMode"> <value>true</value> </property> <property name="connectionPoolSize"> <value>3</value> </property> <property name="validatingQuery"> <value>SELECT 1</value> </property> </bean> <bean id="SDS2" class="com.atomikos.jdbc.SimpleDataSourceBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName"> <value>SDS2</value> </property> <property name="xaDataSourceClassName"> <value>${jdbc.SDS2.class}</value> </property> <property name="xaDataSourceProperties"> <value>${jdbc.SDS2.properties}</value> </property> <property name="exclusiveConnectionMode"> <value>true</value> </property> <property name="connectionPoolSize"> <value>3</value> </property> <property name="validatingQuery"> <value>SELECT 1</value> </property> </bean> <!-- sessionFactory http://sjsky.iteye.com--> <bean id="sessionFactory1" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="SDS" /> <property name="configLocation" value="classpath:jta1.hibernate.cfg.xml" /> </bean> <bean id="sessionFactory2" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="SDS2" /> <property name="configLocation" value="classpath:jta2.hibernate.cfg.xml" /> </bean> <!-- TransactionManager http://sjsky.iteye.com--> <!-- Construct Atomikos UserTransactionManager, needed to configure Spring --> <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close"> <!-- when close is called, should we force transactions to terminate or not? --> <property name="forceShutdown"> <value>true</value> </property> </bean> <!-- Also use Atomikos UserTransactionImp, needed to configure Spring --> <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp"> <property name="transactionTimeout"> <value>300</value> </property> </bean> <!-- Configure the Spring framework to use JTA transactions from Atomikos --> <bean id="springJTATransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManager"> <ref bean="atomikosTransactionManager" /> </property> <property name="userTransaction"> <ref bean="atomikosUserTransaction" /> </property> </bean> <!-- Configure DAO http://sjsky.iteye.com--> <bean id="userInfoDao" class="michael.jta.atomikos.dao.impl.UserInfoDaoImpl"> <property name="sessionFactory" ref="sessionFactory1" /> </bean> <bean id="bankAccountDao" class="michael.jta.atomikos.dao.BankAccountDao"> <property name="sessionFactory" ref="sessionFactory2" /> </bean> <bean id="bankAccountService" class="michael.jta.atomikos.service.impl.BankAccountServiceImpl"> <property name="userInfoDao" ref="userInfoDao" /> <property name="bankAccountDao" ref="bankAccountDao" /> </bean> <!-- 定义事务规则的拦截器 http://sjsky.iteye.com--> <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager" ref="springJTATransactionManager" /> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <!-- 声明式事务边界配置 所有的bean公用一个代理bean http://sjsky.iteye.com--> <bean id="baseTransactionProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true"> <property name="transactionManager" ref="springJTATransactionManager" /> <property name="transactionAttributes"> <props> <!-- 可以根据实际情况细化配置提高性能 --> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <bean id="bankAccountServiceProxy" parent="baseTransactionProxy"> <property name="target"> <ref bean="bankAccountService" /> </property> </bean> </beans>
六、测试验证
1. 初始化数据:
因为mysql数据库表的类型有事务和非事务之分,建表时一定要注意确保表的类型是事务控制的:InnoDB
数据库A(192.168.8.253):
DROP DATABASE IF EXISTS demota; CREATE DATABASE demota; USE demota; DROP TABLE IF EXISTS tb_user_info; CREATE TABLE tb_user_info ( user_name varchar(20), real_name varchar(10), remark1 varchar(50), remark2 varchar(50) ) ENGINE = InnoDB; INSERT INTO tb_user_info (user_name,real_name,remark1,remark2) VALUES ('husband','husband','',''); INSERT INTO tb_user_info (user_name,real_name,remark1,remark2) VALUES ('wife','wife','','');
数据库B(192.168.8.150):
DROP DATABASE IF EXISTS demota; CREATE DATABASE demota; USE demota; DROP TABLE IF EXISTS tb_account; CREATE TABLE tb_account ( id int AUTO_INCREMENT, user_name varchar(20), deposit float(10,2), PRIMARY KEY(id) ) ENGINE = InnoDB; INSERT INTO tb_account (user_name,deposit) VALUES ('husband',20000.00); INSERT INTO tb_account (user_name,deposit) VALUES ('wife',10000.00);
2. 测试过程:
ps: 代码中模拟了异常出现的条件:如果账户金额<18000会抛出异常
JtaRunMainTest.java
package michael.jta.atomikos; import michael.jta.atomikos.service.BankAccountService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author michael * */ public class JtaRunMainTest { /** * @param args */ public static void main(String[] args) { System.out.println("------------start"); ApplicationContext appCt = new ClassPathXmlApplicationContext( "jta.spring.xml"); System.out.println("------------finished init xml"); Object bean = appCt.getBean("bankAccountServiceProxy"); System.out.println(bean.getClass()); BankAccountService service = (BankAccountService) bean; service.doTestTransfer("husband", "wife", 2000); } }
运行第一次结果:
运行第二次结果:
测试过程中数据库查询的结果截图:
从上面的数据库截图可见,第一正常运行时两个数据库同步更新了,第二次运行发生异常后,两个数据库的数据为发生变化,实现了事务回滚。
转载请注明来自:Michael's blog @ http://sjsky.iteye.com
-----------------------------------分 ------------------------------------隔 ------------------------------------线 --------------------------------------