背景描述:我们平时的工作中用到的Spring事务管理是管理一个数据源的。但是如果对多个数据源进行事务管理该怎么办呢?我们可以用JTA和Atomikos结合Spring来实现一个分布式事务管理的功能。
事务(官方解释):是由一组sql语句组成的“逻辑处理单元”。
事务具有如下四个属性,通常称为事务的ACID属性 :
1. 原子性(Atomicity): 事务是一个原子操作单元,要么都执行,要么都不执行。
2. 一致性(Consistent):在事务开始和完成时,数据都必须保持一致。
3. 隔离性(Isoation): 数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。
4. 持久性(Durabe): 事务完成之后,它对数据的修改是永久性的。
分布式事务 : 分布式事务就是指事务的参与者,支持事务的服务器,资源服务器,以及事务管理器分别位于不同的分布式系统的不同节点之上。
本质上来说,分布式事务就是为了保证“不同数据库的数据一致性” 。
分布式事务管理器 :
XA 协议 是可以在数据库conmit 之后进行回滚的。
XA:XA是一个分布式事务协议,由事务管理器和本地资源管理器两部分组成。其中本地资源管理器往往由数据库实现,比如Oracle,DB2这些商业数据库都实现了XA接口。事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。
通过日志记录操作,从上到下任何一步有问题,就会回滚。
Atomikos优点:
XA协议比较简单,使用分布式事务的成本比较低;缺点:性能不理想,XA无法满足高并发场景,许多noSQL也没有支持XA。
Atomikos是一个为java平台提供的开源的事务管理器,主要实现了1:全面崩溃/重启恢复;2:兼容标准的SUN公司JTA API;3:嵌套事务;4:为XA和非XA提供内置的JDBC适配器。
具体实现:
首先需要下载Atomikos需要的jar包:https://download.csdn.net/download/u013310119/10795168
步骤二:准备配置文件。datasource.properties,jta.properties
datasource.properties
#数据源A
dataSource.oracle.driver=oracle.jdbc.driver.OracleDriver
dataSource.oracle.url=jdbc:oracle:thin:@**.19.**.101:1521:xypjcp1
dataSource.oracle.username=inf_nsxycs
dataSource.oracle.password=inf_nsxycs
#数据源B
#org.loushang.persistent.jdbc.datasource.PropertyDataSourceFactoryImpl
dataSource.oracle.nw.url=jdbc:oracle:thin:@**.19.**.91:1521:orcl
dataSource.oracle.nw.username=yhzx
dataSource.oracle.nw.password=yhzx
jta.properties
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
步骤三:在datasource.xml中配置两个数据源
<?xml version="1.0" encoding="UTF-8"?>
<!-- wbw 2016.8.22 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd">
<!-- 多个数据源的公用配置,方便下面直接引用 -->
<bean id="abstractXADataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean"
destroy-method="close">
<property name="xaDataSourceClassName" value="oracle.jdbc.xa.client.OracleXADataSource"/>
<property name="poolSize" value="30" />
<property name="minPoolSize" value="30"/>
<property name="maxPoolSize" value="100"/>
<property name="borrowConnectionTimeout" value="600"/>
<property name="reapTimeout" value="200"/>
<property name="maxIdleTime" value="1200"/>
<property name="maintenanceInterval" value="1200" />
<property name="loginTimeout" value="1200"/>
<property name="logWriter" value="1200"/>
<property name="testQuery">
<value>SELECT * from dual</value>
</property>
</bean>
<!--数据源A-->
<bean id="dataSourcegs3" parent="abstractXADataSource">
<property name="uniqueResourceName" value="dataSourcegs3" />
<property name="xaDataSourceClassName"
value="oracle.jdbc.xa.client.OracleXADataSource" />
<property name="xaProperties">
<props>
<prop key="URL">${dataSource.oracle.gs3.url}</prop>
<prop key="user">${dataSource.oracle.gs3.username}</prop>
<prop key="password">${dataSource.oracle.gs3.password}</prop>
<prop key="pinGlobalTxToPhysicalConnection">true</prop>
</props>
</property>
</bean>
<!--数据源B-->
<bean id="dataSourceds3" parent="abstractXADataSource">
<property name="uniqueResourceName" value="dataSourceds3" />
<property name="xaDataSourceClassName"
value="oracle.jdbc.xa.client.OracleXADataSource" />
<property name="xaProperties">
<props>
<prop key="URL">${dataSource.oracle.ds3.url}</prop>
<prop key="user">${dataSource.oracle.ds3.username}</prop>
<prop key="password">${dataSource.oracle.ds3.password}</prop>
<prop key="pinGlobalTxToPhysicalConnection">true</prop>
</props>
</property>
</bean>
</beans>
步骤四:配置分布式事务
<!-- jta配置开始 -->
<bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
init-method="init" destroy-method="close">
<property name="forceShutdown">
<value>true</value>
</property>
</bean>
<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
<property name="transactionTimeout" value="300" />
</bean>
<bean id="springTransactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager">
<ref bean="atomikosTransactionManager" />
</property>
<property name="userTransaction">
<ref bean="atomikosUserTransaction" />
</property>
<!-- 必须设置,否则程序出现异常 JtaTransactionManager does not support custom isolation levels by default -->
<property name="allowCustomIsolationLevels" value="true" />
</bean>
<!-- jta配置结束 -->
<!-- 配置事务管理 -->
<tx:annotation-driven transaction-manager="springTransactionManager" proxy-target-class="true" />
步骤五、配置mybatis
<bean id="sqlSessionFactorygs3" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSourcegs3" />
<property name="databaseIdProvider" ref="databaseIdProvider" />
<property name="mapperLocations">
<list>
<value>classpath:com/inspur/ahgs3/dao/mapper/*.xml</value>
</list>
</property>
<property name="configLocation" value="/WEB-INF/spring/mybatis-config.xml" />
</bean>
<bean id="sqlSessionFactoryds3" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSourceds3" />
<property name="databaseIdProvider" ref="databaseIdProvider" />
<property name="mapperLocations">
<list>
<value>classpath:com/inspur/ahds3/dao/mapper/*.xml</value>
</list>
</property>
<property name="configLocation" value="/WEB-INF/spring/mybatis-config.xml" />
</bean>
配置mybatis映射文件自动扫描
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.inspur.ahgs3.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactorygs3" />
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.inspur.ahds3.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryds3" />
</bean>
步骤六:Service层代码
@Service
public class Gs3Service {
@Autowired
private gs3dao g3dao;
@Autowired
private ds3dao d3dao;
@Transactional(value="springTransactionManager",rollbackFor=Exception.class)
public void findAll(Map<String, String> bodyMap){
Map<String,String> bodyMap1 = new HashMap<String,String>();
bodyMap1.put("id", "111");
bodyMap1.put("name", "lixiao");
bodyMap1.put("age", "23");
g3dao.insert( bodyMap);
d3dao.insert(bodyMap1);
}
}
步骤七:编写测试Controller
@Controller
@RequestMapping("TestJTATransactionManager")
public class TestJTATransactionManager{
private static Logger logger = LoggerFactory.getLogger(TestJTATransactionManager.class);
@Autowired
private Gs3Service Gs3;
@ResponseBody
@RequestMapping(value="/TestJTAT",produces = "text/plain;charset=utf-8")
public void sendSQLMessage(HttpServletRequest request, HttpServletResponse response) throws NamingException, JMSException, InterruptedException, ParseException{
Map<String,String> bodyMap = new HashMap<String,String>();
bodyMap.put("id", "110");
bodyMap.put("name", "lixiao");
bodyMap.put("age", "23");
Gs3.insertAll(bodyMap);
}
}
model 和 mapper 没什么可说的,这里就不粘贴了。
分别在数据源A和数据源B中创建Student表,其中一个数据源B中Student表主键为id。第一次运行http://localhost:7001/ahyhzx/service/TestJTATransactionManager/TestJTAT.do则两个库中Student表同时插入一条数据,在此运行,数据源B后台报错:主键冲突。这是发现数据源A中student表中同样没有插入数据,两个数据源同时回滚。
后续备注:
如果操作的数据源为MySQL数据库,则xaDataSourceClassName的值设置为:com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
配置参考如下示例:
<bean id="abstractXADataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init"
destroy-method="close" abstract="true">
<property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/>
<property name="poolSize" value="10" />
<property name="minPoolSize" value="10"/>
<property name="maxPoolSize" value="30"/>
<property name="borrowConnectionTimeout" value="60"/>
<property name="reapTimeout" value="20"/>
<!-- 最大空闲时间 -->
<property name="maxIdleTime" value="60"/>
<property name="maintenanceInterval" value="60"/>
<property name="loginTimeout" value="60"/>
<property name="testQuery">
<value>select 1</value>
</property>
</bean>
<bean id="qadataSource" parent="abstractXADataSource">
<!-- value只要两个数据源不同就行,随便取名 -->
<property name="uniqueResourceName" value="mysql/sitestone1" />
<property name="xaDataSourceClassName"
value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
<property name="xaProperties">
<props>
<prop key="URL">${qa.db.url}</prop>
<prop key="user">${qa.db.user}</prop>
<prop key="password">${qa.db.password}</prop>
<prop key="pinGlobalTxToPhysicalConnection">true</prop>
</props>
</property>
</bean>
<bean id="devdataSource" parent="abstractXADataSource">
<!-- value只要两个数据源不同就行,随便取名 -->
<property name="uniqueResourceName" value="mysql/sitestone" />
<property name="xaDataSourceClassName"
value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
<property name="xaProperties">
<props>
<prop key="URL">${dev.db.url}</prop>
<prop key="user">${dev.db.user}</prop>
<prop key="password">${dev.db.password}</prop>
<prop key="pinGlobalTxToPhysicalConnection">true</prop>
</props>
</property>
</bean>
如果发现atomikos 配置好后 @transactional 注解不生效的问题可以参考下面博客:
https://blog.csdn.net/u011696259/article/details/71603480
最近准备了一个公众号每天都会推送一些开发中经常遇到的问题解决方法,希望多久关注一下,谢谢支持: