本文通过一个demo,介绍如何使用spring+mybatis+atomikos+tomcat构建在一个事务中涉及两个数据源的web应用。
demo功能:实现一个能成功提交和回滚的涉及两个数据库数据源的XA事务。
demo将实现:
1.一次性在两个数据库的两张表中各插入一条数据并提交。
2.一次性在两个数据库的两张表中各插入一条数据并回滚。
测试方式:restful web api
使用工具:
spring 4.1.1.RELEASE
mybatis 3.2.7
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) );
代码分析:
本项目使用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:component-scan base-package="com.xy"> 10 <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> 11 </context:component-scan> 12 <context:property-placeholder location="classpath:context/database.properties"/> 13 <tx:annotation-driven/> 14 15 <bean id="abstractXADataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" 16 destroy-method="close" abstract="true"> 17 <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/> 18 <property name="poolSize" value="10" /> 19 <property name="minPoolSize" value="10"/> 20 <property name="maxPoolSize" value="30"/> 21 <property name="borrowConnectionTimeout" value="60"/> 22 <property name="reapTimeout" value="20"/> 23 <!-- 最大空闲时间 --> 24 <property name="maxIdleTime" value="60"/> 25 <property name="maintenanceInterval" value="60"/> 26 <property name="loginTimeout" value="60"/> 27 <property name="testQuery"> 28 <value>select 1</value> 29 </property> 30 </bean> 31 32 <bean id="qadataSource" parent="abstractXADataSource"> 33 <!-- value只要两个数据源不同就行,随便取名 --> 34 <property name="uniqueResourceName" value="mysql/sitestone1" /> 35 <property name="xaDataSourceClassName" 36 value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> 37 <property name="xaProperties"> 38 <props> 39 <prop key="URL">${qa.db.url}</prop> 40 <prop key="user">${qa.db.user}</prop> 41 <prop key="password">${qa.db.password}</prop> 42 <prop key="pinGlobalTxToPhysicalConnection">true</prop> 43 </props> 44 </property> 45 </bean> 46 47 <bean id="devdataSource" parent="abstractXADataSource"> 48 <!-- value只要两个数据源不同就行,随便取名 --> 49 <property name="uniqueResourceName" value="mysql/sitestone" /> 50 <property name="xaDataSourceClassName" 51 value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> 52 <property name="xaProperties"> 53 <props> 54 <prop key="URL">${dev.db.url}</prop> 55 <prop key="user">${dev.db.user}</prop> 56 <prop key="password">${dev.db.password}</prop> 57 <prop key="pinGlobalTxToPhysicalConnection">true</prop> 58 </props> 59 </property> 60 </bean> 61 62 63 64 <bean id="qasqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 65 <property name="dataSource" ref="qadataSource" /> 66 <property name="mapperLocations" value="classpath*:com/xy/dao/*.xml" /> 67 </bean> 68 69 <bean id="devsqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 70 <property name="dataSource" ref="devdataSource" /> 71 <property name="mapperLocations" value="classpath*:com/xy/daodev/*.xml" /> 72 </bean> 73 74 <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" 75 init-method="init" destroy-method="close"> 76 <property name="forceShutdown"> 77 <value>true</value> 78 </property> 79 </bean> 80 <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp"> 81 <property name="transactionTimeout" value="300" /> 82 </bean> 83 84 <bean id="transactionManager" 85 class="org.springframework.transaction.jta.JtaTransactionManager"> 86 <property name="transactionManager"> 87 <ref bean="atomikosTransactionManager"/> 88 </property> 89 <property name="userTransaction"> 90 <ref bean="atomikosUserTransaction"/> 91 </property> 92 <!-- 必须设置,否则程序出现异常 JtaTransactionManager does not support custom isolation levels by default --> 93 <property name="allowCustomIsolationLevels" value="true"/> 94 95 </bean> 96 97 98 99 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> 100 <property name="basePackage" value="com.xy.dao"/> 101 <property name="sqlSessionFactoryBeanName" value="qasqlSessionFactory" /> 102 </bean> 103 104 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> 105 <property name="basePackage" value="com.xy.daodev"/> 106 <property name="sqlSessionFactoryBeanName" value="devsqlSessionFactory" /> 107 </bean> 108 </beans>
其中qadataSource和devdataSource是对应两个数据库的数据源,qasqlSessionFactory和devsqlSessionFactory是mybatis的sessionfactory,两个MapperScannerConfigurer自动将不同数据源的sql语句文件与interface自动装配起来,atomikosTransactionManager会自动管理两个atomikos的数据源的事务,即resource manager,atomikosUserTransaction为最上层的事务管理器为transaction manager。(关于RM和TM,请参见上篇博文)。
Model类如下:package com.xy.model
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 package com.xy.model; 2 3 /** 4 * Created by helloworld on 2015/1/30. 5 */ 6 public class NameQa { 7 private long id; 8 private String nameQa; 9 10 public long getId() { 11 return id; 12 } 13 14 public void setId(long id) { 15 this.id = id; 16 } 17 18 public String getNameQa() { 19 return nameQa; 20 } 21 22 public void setNameQa(String nameQa) { 23 this.nameQa = nameQa; 24 } 25 }
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 package com.xy.model; 2 3 /** 4 * Created by helloworld on 2015/1/30. 5 */ 6 public class NameDev { 7 private long id; 8 private String nameDev; 9 10 public long getId() { 11 return id; 12 } 13 14 public void setId(long id) { 15 this.id = id; 16 } 17 18 public String getNameDev() { 19 return nameDev; 20 } 21 22 public void setNameDev(String nameDev) { 23 this.nameDev = nameDev; 24 } 25 }
qa数据源的mybatis mapper接口 package com.xy.dao
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 package com.xy.dao; 2 3 import com.xy.model.NameQa; 4 5 /** 6 * Created by helloworld on 2015/1/30. 7 */ 8 public interface NameQaMapper { 9 int insert(NameQa nameQa); 10 }
dev数据源的mybatis mapper接口 package com.xy.devdao
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 package com.xy.daodev; 2 3 import com.xy.model.NameDev; 4 5 /** 6 * Created by helloworld on 2015/1/30. 7 */ 8 public interface NameDevMapper { 9 int insert(NameDev nameDev); 10 }
处理事务的service
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 package com.xy.service; 2 3 import com.xy.dao.NameQaMapper; 4 import com.xy.daodev.NameDevMapper; 5 import com.xy.model.NameDev; 6 import com.xy.model.NameQa; 7 import org.springframework.beans.factory.annotation.Autowired; 8 import org.springframework.stereotype.Service; 9 import org.springframework.transaction.annotation.Transactional; 10 11 /** 12 * Created by helloworld on 2015/1/30. 13 */ 14 @Service 15 public class NameService { 16 @Autowired 17 NameQaMapper nameQaMapper; 18 @Autowired 19 NameDevMapper nameDevMapper; 20 21 @Transactional(rollbackFor = Exception.class) 22 public void addQaAndDev(boolean hasException) throws Exception { 23 NameQa nameQa = new NameQa(); 24 nameQa.setNameQa("qa"); 25 nameQaMapper.insert(nameQa); 26 27 NameDev nameDev = new NameDev(); 28 nameDev.setNameDev("dev"); 29 nameDevMapper.insert(nameDev); 30 31 if(hasException) { 32 throw new Exception(); 33 } 34 } 35 36 37 }
controller代码
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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 * Created by helloworld on 2014/11/22. 13 */ 14 @Controller 15 public class mybatisController { 16 17 @Autowired 18 NameService nameService; 19 20 @RequestMapping(value = "/addName", method = RequestMethod.POST) 21 ModelMap addName(@RequestParam("hasException") boolean hasException) { 22 try { 23 nameService.addQaAndDev(hasException); 24 } catch (Exception e) { 25 e.printStackTrace(); 26 return new ModelMap("false"); 27 } 28 return new ModelMap("true"); 29 } 30 31 32 }
将项目打成war包,命名为mybatis.war部署在tomcat上。
测试:
1.POST http://localhost:8080/mybatis/addName.json
request parameters: hasException=false
返回:true 数据添加成功
2.POST http://localhost:8080/mybatis/addName.json
request parameters: hasException=true
返回:false 两个数据库数据都未添加
源码下载:http://files.cnblogs.com/files/rain-in-sun/springmvc-mybatis-atomikos.rar