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