本文通过一个demo,介绍如何使用spring+hibernate+atomikos+tomcat构建在一个事务中涉及两个数据源的web应用。
demo功能:实现一个能成功提交和回滚的涉及两个数据库数据源的XA事务。
demo将实现:
1.一次性在两个数据库的两张表中各插入一条数据并提交。
2.一次性在两个数据库的两张表中各插入一条数据并回滚。
测试方式:restful web api
使用工具:
spring 4.1.1.RELEASE
hibernate 4.2.4.Final
atomikos 3.7.0
tomcat 7
在mysql中建立两个schema,分别为dev和qa。并在里面分别建立一张名字表。
schema:dev
table:namaDev
id | nameDev
scheme:qa
table:nameQa
id | nameQa
对应的sql为
1 CREATE SCHEMA `qa` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ; 2 CREATE SCHEMA `dev` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ; 3 4 CREATE TABLE `dev`.`nameDev` ( 5 `id` BIGINT NOT NULL AUTO_INCREMENT , 6 `nameDev` VARCHAR(45) NULL , 7 PRIMARY KEY (`id`) , 8 UNIQUE INDEX `id_UNIQUE` (`id` ASC) ); 9 10 CREATE TABLE `qa`.`nameQa` ( 11 `id` BIGINT NOT NULL AUTO_INCREMENT , 12 `nameQa` VARCHAR(45) NULL , 13 PRIMARY KEY (`id`) , 14 UNIQUE INDEX `id_UNIQUE` (`id` ASC) );
create table
代码分析:
本项目使用spring框架,因此首先配置相关bean
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" 4 xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc" 5 xmlns:rabbit="http://www.springframework.org/schema/rabbit" 6 xmlns:cache="http://www.springframework.org/schema/cache" xmlns:task="http://www.springframework.org/schema/task" 7 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd 8 http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd"> 9 <context:property-placeholder file-encoding="UTF-8" ignore-resource-not-found="true" 10 location="classpath*:context/database.properties"/> 11 <context:component-scan base-package="com.xy"> 12 <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> 13 </context:component-scan> 14 <tx:annotation-driven/> 15 16 17 18 <bean id="abstractXADataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" 19 destroy-method="close" abstract="true"> 20 <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/> 21 <property name="poolSize" value="10" /> 22 <property name="minPoolSize" value="10"/> 23 <property name="maxPoolSize" value="30"/> 24 <property name="borrowConnectionTimeout" value="60"/> 25 <property name="reapTimeout" value="20"/> 26 <!-- 最大空闲时间 --> 27 <property name="maxIdleTime" value="60"/> 28 <property name="maintenanceInterval" value="60"/> 29 <property name="loginTimeout" value="60"/> 30 <property name="testQuery"> 31 <value>select 1</value> 32 </property> 33 </bean> 34 35 <bean id="qadataSource" parent="abstractXADataSource"> 36 <!-- value只要两个数据源不同就行,随便取名 --> 37 <property name="uniqueResourceName" value="mysql/sitestone" /> 38 <property name="xaDataSourceClassName" 39 value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> 40 <property name="xaProperties"> 41 <props> 42 <prop key="URL">${qa.db.url}</prop> 43 <prop key="user">${qa.db.user}</prop> 44 <prop key="password">${qa.db.password}</prop> 45 <prop key="pinGlobalTxToPhysicalConnection">true</prop> 46 </props> 47 </property> 48 </bean> 49 50 <bean id="devdataSource" parent="abstractXADataSource"> 51 <!-- value只要两个数据源不同就行,随便取名 --> 52 <property name="uniqueResourceName" value="mysql/sitestone1" /> 53 <property name="xaDataSourceClassName" 54 value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> 55 <property name="xaProperties"> 56 <props> 57 <prop key="URL">${dev.db.url}</prop> 58 <prop key="user">${dev.db.user}</prop> 59 <prop key="password">${dev.db.password}</prop> 60 <prop key="pinGlobalTxToPhysicalConnection">true</prop> 61 </props> 62 </property> 63 </bean> 64 65 <bean id="qaSessionFactory" 66 class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> 67 <property name="packagesToScan" value="com.xy.model"/> 68 <property name="dataSource" ref="qadataSource"/> 69 <property name="hibernateProperties"> 70 <props> 71 <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop> 72 <prop key="hibernate.show_sql">false</prop> 73 <prop key="hibernate.autoReconnect">true</prop> 74 <prop key="hibernate.connection.release_mode">after_transaction</prop> 75 </props> 76 </property> 77 </bean> 78 <bean id="devSessionFactory" 79 class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> 80 <property name="packagesToScan" value="com.xy.model"/> 81 <property name="dataSource" ref="devdataSource"/> 82 <property name="hibernateProperties"> 83 <props> 84 <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop> 85 <prop key="hibernate.show_sql">false</prop> 86 <prop key="hibernate.autoReconnect">true</prop> 87 <prop key="hibernate.connection.release_mode">after_transaction</prop> 88 </props> 89 </property> 90 </bean> 91 92 <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" 93 init-method="init" destroy-method="close"> 94 <property name="forceShutdown"> 95 <value>true</value> 96 </property> 97 </bean> 98 <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp"> 99 <property name="transactionTimeout" value="300" /> 100 </bean> 101 102 <bean id="transactionManager" 103 class="org.springframework.transaction.jta.JtaTransactionManager"> 104 <property name="transactionManager"> 105 <ref bean="atomikosTransactionManager"/> 106 </property> 107 <property name="userTransaction"> 108 <ref bean="atomikosUserTransaction"/> 109 </property> 110 <!-- 必须设置,否则程序出现异常 JtaTransactionManager does not support custom isolation levels by default --> 111 <property name="allowCustomIsolationLevels" value="true"/> 112 113 </bean> 114 115 </beans>
其中qadataSource和devdataSource是对应两个数据库的数据源,qaSessionFactory和devSessionFactory是hibernate的sessionfactory。atomikosTransactionManager会自动管理两个atomikos的数据源的事务,即resource manager,atomikosUserTransaction为最上层的事务管理器为transaction manager。(关于RM和TM,请参见上篇博文)。
Model类如下:package com.xy.model
package com.xy.model; import javax.persistence.*; /** * Created by helloworld on 2015/1/30. */ @Entity @Table(name = "nameDev") public class NameDev { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String nameDev; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getNameDev() { return nameDev; } public void setNameDev(String nameDev) { this.nameDev = nameDev; } }
NameDev
1 package com.xy.model; 2 3 4 import javax.persistence.*; 5 6 /** 7 * Created by helloworld on 2015/1/30. 8 */ 9 @Entity 10 @Table(name = "nameQa") 11 public class NameQa { 12 @Id 13 @GeneratedValue(strategy = GenerationType.AUTO) 14 private long id; 15 private String nameQa; 16 17 public long getId() { 18 return id; 19 } 20 21 public void setId(long id) { 22 this.id = id; 23 } 24 25 public String getNameQa() { 26 return nameQa; 27 } 28 29 public void setNameQa(String nameQa) { 30 this.nameQa = nameQa; 31 } 32 }
nameQa
处理事务的service
1 package com.xy.service; 2 3 import com.xy.model.NameDev; 4 import com.xy.model.NameQa; 5 import org.hibernate.SessionFactory; 6 import org.springframework.beans.factory.annotation.Autowired; 7 import org.springframework.stereotype.Service; 8 import org.springframework.transaction.annotation.Transactional; 9 10 /** 11 * Created by helloworld on 2015/1/30. 12 */ 13 @Service 14 public class NameService { 15 @Autowired 16 private SessionFactory qaSessionFactory; 17 @Autowired 18 private SessionFactory devSessionFactory; 19 20 @Transactional(rollbackFor = Exception.class) 21 public void addQaAndDev(boolean hasException) throws Exception { 22 NameQa nameQa = new NameQa(); 23 nameQa.setNameQa("hello"); 24 qaSessionFactory.getCurrentSession().save(nameQa); 25 26 NameDev nameDev = new NameDev(); 27 nameDev.setNameDev("hello"); 28 devSessionFactory.getCurrentSession().save(nameDev); 29 30 if(hasException){ 31 throw new Exception(); 32 } 33 34 } 35 36 37 }
service
controller代码
1 package com.xy.controller; 2 3 import com.xy.service.NameService; 4 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.stereotype.Controller; 6 import org.springframework.ui.ModelMap; 7 import org.springframework.web.bind.annotation.RequestMapping; 8 import org.springframework.web.bind.annotation.RequestMethod; 9 import org.springframework.web.bind.annotation.RequestParam; 10 11 12 /** 13 * Created by helloworld on 2014/11/22. 14 */ 15 @Controller 16 public class hibernateController { 17 @Autowired 18 NameService nameService; 19 20 21 @RequestMapping(value = "/addName", method = RequestMethod.POST) 22 ModelMap addName(@RequestParam("hasException") boolean hasException){ 23 try { 24 nameService.addQaAndDev(hasException); 25 } catch (Exception e) { 26 e.printStackTrace(); 27 return new ModelMap("false"); 28 } 29 return new ModelMap("true"); 30 } 31 32 33 }
controller
将项目打成war包,命名为test.war部署在tomcat上。
测试:
1.POST http://localhost:8080/test/addName.json
request parameters: hasException=false 返回:true 数据添加成功
2.POST http://localhost:8080/test/addName.json
request parameters: hasException=true
返回:false 两个数据库数据都未添加 源码下载:http://files.cnblogs.com/files/rain-in-sun/springmvc-hibernate-atomikos.rar