4.0 atomikos JTA/XA全局事務(wù)
?2018-02-05 02:47:40??1,720??2
Atomikos公司官方網(wǎng)址為:https://www.atomikos.com/剃根。其旗下最著名的產(chǎn)品就是事務(wù)管理器。產(chǎn)品分兩個(gè)版本:
TransactionEssentials:開源的免費(fèi)產(chǎn)品
ExtremeTransactions:上商業(yè)版拯杠,需要收費(fèi)。
這兩個(gè)產(chǎn)品的關(guān)系如下圖所示:?
TransactionEssentials:
1幽邓、實(shí)現(xiàn)了JTA/XA規(guī)范中的事務(wù)管理器(Transaction Manager)應(yīng)該實(shí)現(xiàn)的相關(guān)接口呜笑,如:
?? ?UserTransaction實(shí)現(xiàn)是com.atomikos.icatch.jta.UserTransactionImp,用戶只需要直接操作這個(gè)類
?? ?TransactionManager實(shí)現(xiàn)是com.atomikos.icatch.jta.UserTransactionManager
?? ?Transaction實(shí)現(xiàn)是com.atomikos.icatch.jta.TransactionImp
2顷锰、針對(duì)實(shí)現(xiàn)了JDBC規(guī)范中規(guī)定的實(shí)現(xiàn)了XADataSource接口的數(shù)據(jù)庫(kù)連接池,以及實(shí)現(xiàn)了JMS規(guī)范的MQ客戶端提供一層封裝亡问。
? ? ?在上一節(jié)我們講解JTA規(guī)范時(shí)官紫,提到過XADataSource肛宋、XAConnection等接口應(yīng)該由資源管理器RM來實(shí)現(xiàn),而Atomikos的作用是一個(gè)事務(wù)管理器(TM)束世,并不需要提供對(duì)應(yīng)的實(shí)現(xiàn)酝陈。而Atomikos對(duì)XADataSource進(jìn)行封裝,只是為了方便與事務(wù)管理器整合毁涉。封裝XADataSource的實(shí)現(xiàn)類為AtomikosDataSourceBean沉帮。典型的XADataSource實(shí)現(xiàn)包括:
? ? 1、mysql官方提供的com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
? ? 2贫堰、阿里巴巴開源的druid連接池穆壕,對(duì)應(yīng)的實(shí)現(xiàn)類為com.alibaba.druid.pool.xa.DruidXADataSource
? ? 3、tomcat-jdbc連接池提供的org.apache.tomcat.jdbc.pool.XADataSource
? ? 而其他一些常用的數(shù)據(jù)庫(kù)連接池其屏,如dbcp喇勋、dbcp2或者c3p0,目前貌似尚未提供XADataSource接口的實(shí)現(xiàn)漫玄。如果提供給AtomikosDataSourceBean一個(gè)沒有實(shí)現(xiàn)XADataSource接口的數(shù)據(jù)源,如c3p0的ComboPooledDataSource压彭,則會(huì)拋出類似以下異常:
com.atomikos.jdbc.AtomikosSQLException:?The?class?'com.mchange.v2.c3p0.ComboPooledDataSource'
?specified?by?property?'xaDataSourceClassName'?does?not?implement?the?required?interface??
?javax.jdbc.XADataSource.??
?Please?make?sure?the?spelling?is?correct,?and?check?your?JDBC?driver?vendor's?documentation.
ExtremeTransactions在TransactionEssentials的基礎(chǔ)上額外提供了以下功能:
支持TCC:這是一種柔性事務(wù)
支持通過RMI睦优、IIOP、SOAP這些遠(yuǎn)程過程調(diào)用技術(shù)壮不,進(jìn)行事務(wù)傳播汗盘。
本文主要針對(duì)Atomikos開源版本的事務(wù)管理器實(shí)現(xiàn)TransactionEssentials進(jìn)行講解,包括:
1询一、直接使用TransactionEssentials的API
2隐孽、TransactionEssentials與spring、mybatis整合
3健蕊、Atomikos配置詳解
直接使用TransactionEssentials的API
在maven項(xiàng)目的pom文件中引入以下依賴:
????com.atomikos
????transactions-jdbc
????4.0.6
????mysql
????mysql-connector-java
????5.1.39
新建mysql數(shù)據(jù)庫(kù)表
需要注意的是菱阵,在mysql中,只有innodb引擎才支持XA事務(wù)缩功,所以這里顯式的指定了數(shù)據(jù)庫(kù)引擎為innodb晴及。
--?新建數(shù)據(jù)庫(kù)db_user;
create?database?db_user;
--?在db_user庫(kù)中新建user表
create?table?db_user.user(id?int?AUTO_INCREMENT?PRIMARY?KEY,name?varchar(50))?engine=innodb;
--?新建數(shù)據(jù)庫(kù)db_account;
create?database?db_account;
--?在db_account庫(kù)中新建account表
create?table?db_account.account(user_id?int,money?double)?engine=innodb;
另外,在本案例中嫡锌,db_user庫(kù)和db_account庫(kù)是位于同一個(gè)mysql實(shí)例中的虑稼。?
案例代碼:
?? ?在使用了事務(wù)管理器之后,我們通過atomikos提供的UserTransaction接口的實(shí)現(xiàn)類com.atomikos.icatch.jta.UserTransactionImp來開啟势木、提交和回滾事務(wù)蛛倦。而不再是使用java.sql.Connection中的setAutoCommit(false)的方式來開啟事務(wù)。其他JTA規(guī)范中定義的接口啦桌,開發(fā)人員并不需要直接使用溯壶。
import?com.atomikos.icatch.jta.UserTransactionImp;
import?com.atomikos.jdbc.AtomikosDataSourceBean;
import?javax.transaction.SystemException;
import?javax.transaction.UserTransaction;
import?java.sql.Connection;
import?java.sql.PreparedStatement;
import?java.sql.ResultSet;
import?java.sql.Statement;
import?java.util.Properties;
public?class?AtomikosExample?{
???private?static?AtomikosDataSourceBean?createAtomikosDataSourceBean(String?dbName)?{
??????//?連接池基本屬性
??????Properties?p?=?new?Properties();
??????p.setProperty("url",?"jdbc:mysql://localhost:3306/"?+?dbName);
??????p.setProperty("user",?"root");
??????p.setProperty("password",?"your?password");
??????//?使用AtomikosDataSourceBean封裝com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
??????AtomikosDataSourceBean?ds?=?new?AtomikosDataSourceBean();
??????//atomikos要求為每個(gè)AtomikosDataSourceBean名稱,為了方便記憶,這里設(shè)置為和dbName相同
??????ds.setUniqueResourceName(dbName);
??????ds.setXaDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlXADataSource");
??????ds.setXaProperties(p);
??????return?ds;
???}
???public?static?void?main(String[]?args)?{
??????AtomikosDataSourceBean?ds1?=?createAtomikosDataSourceBean("db_user");
??????AtomikosDataSourceBean?ds2?=?createAtomikosDataSourceBean("db_account");
??????Connection?conn1?=?null;
??????Connection?conn2?=?null;
??????PreparedStatement?ps1?=?null;
??????PreparedStatement?ps2?=?null;
??????UserTransaction?userTransaction?=?new?UserTransactionImp();
??????try?{
?????????//?開啟事務(wù)
?????????userTransaction.begin();
?????????//?執(zhí)行db1上的sql
?????????conn1?=?ds1.getConnection();
?????????ps1?=?conn1.prepareStatement("INSERT?into?user(name)?VALUES?(?)",?Statement.RETURN_GENERATED_KEYS);
?????????ps1.setString(1,?"tianshouzhi");
?????????ps1.executeUpdate();
?????????ResultSet?generatedKeys?=?ps1.getGeneratedKeys();
?????????int?userId?=?-1;
?????????while?(generatedKeys.next())?{
????????????userId?=?generatedKeys.getInt(1);//?獲得自動(dòng)生成的userId
?????????}
?????????//?模擬異常?茸塞,直接進(jìn)入catch代碼塊躲庄,2個(gè)都不會(huì)提交
//????????int?i=1/0;
?????????//?執(zhí)行db2上的sql
?????????conn2?=?ds2.getConnection();
?????????ps2?=?conn2.prepareStatement("INSERT?into?account(user_id,money)?VALUES?(?,?)");
?????????ps2.setInt(1,?userId);
?????????ps2.setDouble(2,?10000000);
?????????ps2.executeUpdate();
?????????//?兩階段提交
?????????userTransaction.commit();
??????}?catch?(Exception?e)?{
?????????try?{
????????????e.printStackTrace();
????????????userTransaction.rollback();
?????????}?catch?(SystemException?e1)?{
????????????e1.printStackTrace();
?????????}
??????}?finally?{
?????????try?{
????????????ps1.close();
????????????ps2.close();
????????????conn1.close();
????????????conn2.close();
????????????ds1.close();
????????????ds2.close();
?????????}?catch?(Exception?ignore)?{
?????????}
??????}
???}
}
2、TransactionEssentials與spring钾虐、mybatis整合
在pom中添加以下依賴?
????org.springframework
????spring-jdbc
????4.3.7.RELEASE
????org.springframework
????spring-context
????4.3.7.RELEASE
????org.mybatis
????mybatis
????3.4.1
????org.mybatis
????mybatis-spring
????1.3.1
新建User實(shí)體
package?com.tianshouzhi.atomikos;
public?class?User?{
???private?int?id;
???private?String?name;
???//?setters?and?getters
}
新建Account實(shí)例
package?com.tianshouzhi.atomikos;
public?class?Account?{
???private?int?userId;
???private?double?money;
???//?setters?and?getters
}
新建UserMapper接口噪窘,為了方便,這里使用了mybatis 注解方式效扫,沒有編寫映射文件倔监,作用是一樣的
package?com.tianshouzhi.atomikos.mappers.db_user;
import?org.apache.ibatis.annotations.Insert;
import?com.tianshouzhi.atomikos.User;
import?org.apache.ibatis.annotations.Options;
public?interface?UserMapper?{
???@Insert("INSERT?INTO?user(id,name)?VALUES(#{id},#{name})")
???@Options(useGeneratedKeys?=?true,?keyColumn?=?"id",?keyProperty?=?"id")
???public?void?insert(User?user);
}
新建AccountMapper接口
package?com.tianshouzhi.atomikos.mappers.ds_account;
import?com.tianshouzhi.atomikos.Account;
import?org.apache.ibatis.annotations.Insert;
public?interface?AccountMapper?{
????@Insert("INSERT?INTO?account(user_id,money)?VALUES(#{userId},#{money})")
????public?void?insert(Account?account);
}
新建使用JTA事務(wù)的bean,注意在使用jta事務(wù)的時(shí)候菌仁,依然可以使用spring的聲明式事務(wù)管理
package?com.tianshouzhi.atomikos;
import?com.tianshouzhi.atomikos.mappers.db_user.UserMapper;
import?com.tianshouzhi.atomikos.mappers.ds_account.AccountMapper;
import?org.springframework.beans.factory.annotation.Autowired;
import?org.springframework.transaction.annotation.Transactional;
public?class?JTAService?{
???@Autowired
???private?UserMapper?userMapper;//操作db_user庫(kù)
???@Autowired
???private?AccountMapper?accountMapper;//操作db_account庫(kù)
???@Transactional
???public?void?insert()?{
??????User?user?=?new?User();
??????user.setName("wangxiaoxiao");
??????userMapper.insert(user);
??????//????int?i?=?1?/?0;//模擬異常浩习,spring回滾后,db_user庫(kù)中user表中也不會(huì)插入記錄
??????Account?account?=?new?Account();
??????account.setUserId(user.getId());
??????account.setMoney(123456789);
??????accountMapper.insert(account);
???}
}
編寫配置文件spring-atomikos.xml
???????xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"?xmlns:tx="http://www.springframework.org/schema/tx"
???????xsi:schemaLocation="http://www.springframework.org/schema/beans?http://www.springframework.org/schema/beans/spring-beans.xsd
???????http://www.springframework.org/schema/tx?http://www.springframework.org/schema/tx/spring-tx.xsd">
??????????init-method="init"?destroy-method="close">
????????
??????????????????value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"?/>
????????
????????????
????????????????jdbc:mysql://localhost:3306/db_user
????????????????root
????????????????shxx12151022
??????????init-method="init"?destroy-method="close">
????????
??????????????????value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"?/>
????????
????????????
????????????????jdbc:mysql://localhost:3306/db_account
????????????????root
????????????????shxx12151022
????
????????
????
????????
????
????????
????????
????
????????
????????
??????????destroy-method="close">
????????
????
????????
????
????
測(cè)試代碼
package?com.tianshouzhi.atomikos;
import?org.springframework.context.ApplicationContext;
import?org.springframework.context.support.ClassPathXmlApplicationContext;
public?class?AtomikosSpringMybatisExample?{
???public?static?void?main(String[]?args)?{
??????ApplicationContext?context?=?new?ClassPathXmlApplicationContext("spring-atomikos.xml");
??????JTAService?jtaService?=?context.getBean("jtaService",?JTAService.class);
??????jtaService.insert();
???}
}
????建議讀者先直接按照上述代碼運(yùn)行济丘,以確定代碼執(zhí)行后谱秽,db_user庫(kù)的user表和db_account的account表中的確各插入了一條記錄,以證明我們的代碼的確是操作了2個(gè)庫(kù)摹迷,屬于分布式事務(wù)疟赊。
?? ?然后將JTAService中的異常模擬的注釋打開,會(huì)發(fā)現(xiàn)出現(xiàn)異常后峡碉,兩個(gè)庫(kù)中都沒有新插入的數(shù)據(jù)庫(kù)近哟,說明我們使用的JTA事務(wù)管理器的確保證數(shù)據(jù)的一致性了。
Atomikos配置
?? ?在掌握了Atomikos基本使用之后鲫寄,我們對(duì)Atomikos的配置進(jìn)行一下簡(jiǎn)單的介紹吉执。Atomikos在啟動(dòng)后,默認(rèn)會(huì)從以下幾個(gè)位置讀取配置文件地来,這里筆者直接貼出atomikos源碼進(jìn)行說明:
com.atomikos.icatch.provider.imp.AssemblerImp#initializeProperties方法中定義了配置加載順序邏輯:?
@Override
????public?ConfigProperties?initializeProperties()?{
????????//讀取classpath下的默認(rèn)配置transactions-defaults.properties
????????Properties?defaults?=?new?Properties();
????????loadPropertiesFromClasspath(defaults,?DEFAULT_PROPERTIES_FILE_NAME);
????????//讀取classpath下戳玫,transactions.properties配置,覆蓋transactions-defaults.properties中相同key的值
????????Properties?transactionsProperties?=?new?Properties(defaults);
????????loadPropertiesFromClasspath(transactionsProperties,?TRANSACTIONS_PROPERTIES_FILE_NAME);
????????//讀取classpath下未斑,jta.properties量九,覆蓋transactions-defaults.properties、transactions.properties中相同key的值
????????Properties?jtaProperties?=?new?Properties(transactionsProperties);
????????loadPropertiesFromClasspath(jtaProperties,?JTA_PROPERTIES_FILE_NAME);
????????//讀取通過java?-Dcom.atomikos.icatch.file方式指定的自定義配置文件路徑颂碧,覆蓋之前的同名配置
????????Properties?customProperties?=?new?Properties(jtaProperties);
????????loadPropertiesFromCustomFilePath(customProperties);
????????//最終構(gòu)造一個(gè)ConfigProperties對(duì)象荠列,來表示實(shí)際要使用的配置
????????Properties?finalProperties?=?new?Properties(customProperties);
????????return?new?ConfigProperties(finalProperties);
????}
配置文件優(yōu)先級(jí):transactions-defaults.properties
其中transactions-defaults.properties是atomikos自帶的默認(rèn)配置,位于transactions-xxx.jar中.
注意不同版本的默認(rèn)配置可能不同载城。特別是3.x版本和4.x版本的差異比較明顯肌似。 ??
以下是4.0.6中 transactions-default.properties中配置內(nèi)容,筆者對(duì)這些配置進(jìn)行了歸類诉瓦,如下:?
===============================================================
============??????????事務(wù)管理器(TM)配置參數(shù)???????==============
===============================================================
#指定是否啟動(dòng)磁盤日志川队,默認(rèn)為true力细。在生產(chǎn)環(huán)境下一定要保證為true,否則數(shù)據(jù)的完整性無(wú)法保證
com.atomikos.icatch.enable_logging=true
#JTA/XA資源是否應(yīng)該自動(dòng)注冊(cè)
com.atomikos.icatch.automatic_resource_registration=true
#JTA事務(wù)的默認(rèn)超時(shí)時(shí)間固额,默認(rèn)為10000ms
com.atomikos.icatch.default_jta_timeout=10000
#事務(wù)的最大超時(shí)時(shí)間眠蚂,默認(rèn)為300000ms。這表示事務(wù)超時(shí)時(shí)間由?UserTransaction.setTransactionTimeout()較大者決定斗躏。4.x版本之后逝慧,指定為0的話則表示不設(shè)置超時(shí)時(shí)間
com.atomikos.icatch.max_timeout=300000
#指定在兩階段提交時(shí),是否使用不同的線程(意味著并行)啄糙。3.7版本之后默認(rèn)為false笛臣,更早的版本默認(rèn)為true。如果為false隧饼,則提交將按照事務(wù)中訪問資源的順序進(jìn)行沈堡。
com.atomikos.icatch.threaded_2pc=false
#指定最多可以同時(shí)運(yùn)行的事務(wù)數(shù)量,默認(rèn)值為50燕雁,負(fù)數(shù)表示沒有數(shù)量限制诞丽。在調(diào)用?UserTransaction.begin()方法時(shí),可能會(huì)拋出一個(gè)”Max?number?of?active?transactions?reached”異常信息拐格,表示超出最大事務(wù)數(shù)限制
com.atomikos.icatch.max_actives=50
#是否支持subtransaction僧免,默認(rèn)為true
com.atomikos.icatch.allow_subtransactions=true
#指定在可能的情況下,否應(yīng)該join?子事務(wù)(subtransactions)禁荒,默認(rèn)值為true猬膨。如果設(shè)置為false角撞,對(duì)于有關(guān)聯(lián)的不同subtransactions呛伴,不會(huì)調(diào)用XAResource.start(TM_JOIN)
com.atomikos.icatch.serial_jta_transactions=true
#指定JVM關(guān)閉時(shí)是否強(qiáng)制(force)關(guān)閉事務(wù)管理器,默認(rèn)為false
com.atomikos.icatch.force_shutdown_on_vm_exit=false
#在正常關(guān)閉(no-force)的情況下谒所,應(yīng)該等待事務(wù)執(zhí)行完成的時(shí)間热康,默認(rèn)為L(zhǎng)ong.MAX_VALUE
com.atomikos.icatch.default_max_wait_time_on_shutdown=9223372036854775807
===============================================================
=========????????事務(wù)日志(Transaction?logs)記錄配置???????=======
===============================================================
#事務(wù)日志目錄,默認(rèn)為./劣领。
com.atomikos.icatch.log_base_dir=./
#事務(wù)日志文件前綴姐军,默認(rèn)為tmlog。事務(wù)日志存儲(chǔ)在文件中尖淘,文件名包含一個(gè)數(shù)字后綴奕锌,日志文件以.log為擴(kuò)展名,如tmlog1.log村生。遇到checkpoint時(shí)惊暴,新的事務(wù)日志文件會(huì)被創(chuàng)建,數(shù)字增加趁桃。
com.atomikos.icatch.log_base_name=tmlog
#指定兩次checkpoint的時(shí)間間隔辽话,默認(rèn)為500
com.atomikos.icatch.checkpoint_interval=500
===============================================================
=========??????????事務(wù)日志恢復(fù)(Recovery)配置???????=============
===============================================================
#指定在多長(zhǎng)時(shí)間后可以清空無(wú)法恢復(fù)的事務(wù)日志(orphaned)肄鸽,默認(rèn)86400000ms
com.atomikos.icatch.forget_orphaned_log_entries_delay=86400000
#指定兩次恢復(fù)掃描之間的延遲時(shí)間掖肋。默認(rèn)值為與com.atomikos.icatch.default_jta_timeout相同
com.atomikos.icatch.recovery_delay=${com.atomikos.icatch.default_jta_timeout}
#提交失敗時(shí)辛蚊,再拋出一個(gè)異常之前,最多可以重試幾次预皇,默認(rèn)值為5
com.atomikos.icatch.oltp_max_retries=5
#提交失敗時(shí)益咬,每次重試的時(shí)間間隔逮诲,默認(rèn)10000ms
com.atomikos.icatch.oltp_retry_interval=10000
===============================================================
=========??????????其他???????===============================?==
===============================================================
java.naming.factory.initial=com.sun.jndi.rmi.registry.RegistryContextFactory
com.atomikos.icatch.client_demarcation=false
java.naming.provider.url=rmi://localhost:1099
com.atomikos.icatch.rmi_export_class=none
com.atomikos.icatch.trust_client_tm=false
當(dāng)我們想對(duì)默認(rèn)的配置進(jìn)行修改時(shí),可以在classpath下新建一個(gè)jta.properties础废,覆蓋同名的配置項(xiàng)即可汛骂。
關(guān)于不同版本配置的差異,請(qǐng)參考官方文檔:https://www.atomikos.com/Documentation/JtaProperties
打印日志
4.x版本之后评腺,優(yōu)先嘗試使用slf4j帘瞭,如果沒有則嘗試使用log4j,如果二者都沒有蒿讥,則使用JUL蝶念。
參見:https://www.atomikos.com/Documentation/ConfiguringTheLogs
注意這里是說的是如何配置打印工作日志(work log),而前面說的是事務(wù)日志(transactions log)芋绸,二者不是不同的媒殉。