1. 程式人生 > >Spring+JTA+Atomikos+mybatis分散式事務管理

Spring+JTA+Atomikos+mybatis分散式事務管理

 

背景描述:我們平時的工作中用到的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