此文已由作者夏昀授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
分布式事务对性能有一定的影响,所以不是最佳的解决方案,能通过设计避免最好尽量避免。
分布式事务(Distributed transactions),也称作XA事务(XA是一个协议的名字),在spring中被称作global transaction,是指一个事务会涉及到不同的事务资源,比如不同的数据库,消息队列。事务资源都支持commit和rollback这样的事务控制命令。
按是否需要实现完整javaEE功能的应用服务器,可以将实现XA事务的方法分为两种:
1.一般情况下,XA事务是通过javaEE应用服务器实现的,即CMT(Container Managed Transaction)。应用服务器实现了JTA(Java Transaction API),应用通过JNDI获取应用服务器的JTA UserTransaction。JTA的api比较复杂,开发者需要研究较多JTA的细节,使用spring事务管理可以简化JTA的编程模型。但是这样还是需要依赖实现了对应javaEE功能的应用服务器。
2.不需要应用服务器(standalone),或者只需要轻量级的应用服务器,例如tomcat,tomcat没有实现所有的javaEE功能。在应用中加上支持jta的第三方包,例如atomikos,JOTM等。
分布式事务的结构如图所示:
图中,1表示APP与资源管理器RM之间的接口,是资源管理器的本地接口或者XA接口。如果使用事务管理器TM来管理分布式事务, 则不需要app直接调用rm,即1接口不会使用。 2表示app与TM的接口,即UserTransaction,这个接口和普通的事务管理接口类似,是一个有提交和回滚等操作的接口。使用该接口,就像事务只处理一个数据源一样。app通过该接口控制事务的提交或回滚。 3是TM与RM之间的接口,是一个两阶段提交(2 phase commit)的过程,两阶段提交简单的说就是一个数据源的事务要提交两次才算真正提交。该操作由TM控制,app不直接调用接口3。
下面通过一个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为
CREATE SCHEMA `qa` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ;CREATE SCHEMA `dev` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ; CREATE TABLE `dev`.`nameDev` ( `id` BIGINT NOT NULL AUTO_INCREMENT , `nameDev` VARCHAR(45) NULL , PRIMARY KEY (`id`) , UNIQUE INDEX `id_UNIQUE` (`id` ASC) );
CREATE TABLE `qa`.`nameQa` ( `id` BIGINT NOT NULL AUTO_INCREMENT , `nameQa` VARCHAR(45) NULL , PRIMARY KEY (`id`) , 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。
网易云免费体验馆,0成本体验20+款云产品!
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 MySQL之不得不说的keepsync和trysync