Spring+JTA+Atomikos+mybatis分布式事务管理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013310119/article/details/84280930

背景描述:我们平时的工作中用到的Spring事务管理是管理一个数据源的。但是如果对多个数据源进行事务管理该怎么办呢?我们可以用JTA和Atomikos结合Spring来实现一个分布式事务管理的功能。

事务(官方解释):是由一组sql语句组成的“逻辑处理单元”。

事务具有如下四个属性,通常称为事务的ACID属性 : 
1. 原子性(Atomicity): 事务是一个原子操作单元,要么都执行,要么都不执行。 
2. 一致性(Consistent):在事务开始和完成时,数据都必须保持一致。 
3. 隔离性(Isoation): 数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。 
4. 持久性(Durabe): 事务完成之后,它对数据的修改是永久性的。

分布式事务 : 分布式事务就是指事务的参与者,支持事务的服务器,资源服务器,以及事务管理器分别位于不同的分布式系统的不同节点之上。

本质上来说,分布式事务就是为了保证“不同数据库的数据一致性” 。

分布式事务管理器 :

XA 协议 是可以在数据库conmit 之后进行回滚的。

XA:XA是一个分布式事务协议,由事务管理器和本地资源管理器两部分组成。其中本地资源管理器往往由数据库实现,比如Oracle,DB2这些商业数据库都实现了XA接口。事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。

通过日志记录操作,从上到下任何一步有问题,就会回滚。

Atomikos优点:

扫描二维码关注公众号,回复: 4164874 查看本文章

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

猜你喜欢

转载自blog.csdn.net/u013310119/article/details/84280930