Atomikos公司官方網址為:https://www.atomikos.com/。其旗下最著名的產品就是事務管理器。產品分兩個版本:
TransactionEssentials:開源的免費產品
ExtremeTransactions:上商業版,需要收費。
這兩個產品的關系如下圖所示:?
TransactionEssentials:
1、實現了JTA/XA規范中的事務管理器(Transaction Manager)應該實現的相關接口,如:
?? ?UserTransaction實現是com.atomikos.icatch.jta.UserTransactionImp,用戶只需要直接操作這個類
?? ?TransactionManager實現是com.atomikos.icatch.jta.UserTransactionManager
?? ?Transaction實現是com.atomikos.icatch.jta.TransactionImp
2、針對實現了JDBC規范中規定的實現了XADataSource接口的數據庫連接池,以及實現了JMS規范的MQ客戶端提供一層封裝。
? ? ?在上一節我們講解JTA規范時,提到過XADataSource、XAConnection等接口應該由資源管理器RM來實現,而Atomikos的作用是一個事務管理器(TM),并不需要提供對應的實現。而Atomikos對XADataSource進行封裝,只是為了方便與事務管理器整合。封裝XADataSource的實現類為AtomikosDataSourceBean。典型的XADataSource實現包括:
? ? 1、mysql官方提供的com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
? ? 2、阿里巴巴開源的druid連接池,對應的實現類為com.alibaba.druid.pool.xa.DruidXADataSource
? ? 3、tomcat-jdbc連接池提供的org.apache.tomcat.jdbc.pool.XADataSource
? ? 而其他一些常用的數據庫連接池,如dbcp、dbcp2或者c3p0,目前貌似尚未提供XADataSource接口的實現。如果提供給AtomikosDataSourceBean一個沒有實現XADataSource接口的數據源,如c3p0的ComboPooledDataSource,則會拋出類似以下異常:?
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的基礎上額外提供了以下功能:
支持TCC:這是一種柔性事務
支持通過RMI、IIOP、SOAP這些遠程過程調用技術,進行事務傳播。
本文主要針對Atomikos開源版本的事務管理器實現TransactionEssentials進行講解,包括:
1、直接使用TransactionEssentials的API
2、TransactionEssentials與spring、mybatis整合
3、Atomikos配置詳解
?
直接使用TransactionEssentials的API
在maven項目的pom文件中引入以下依賴:
<dependency>????<groupId>com.atomikos</groupId>????<artifactId>transactions-jdbc</artifactId>????<version>4.0.6</version></dependency><dependency>????<groupId>mysql</groupId>????<artifactId>mysql-connector-java</artifactId>????<version>5.1.39</version></dependency>
新建mysql數據庫表
需要注意的是,在mysql中,只有innodb引擎才支持XA事務,所以這里顯式的指定了數據庫引擎為innodb。
--?新建數據庫db_user;create?database?db_user;--?在db_user庫中新建user表create?table?db_user.user(id?int?AUTO_INCREMENT?PRIMARY?KEY,name?varchar(50))?engine=innodb;--?新建數據庫db_account;create?database?db_account;--?在db_account庫中新建account表create?table?db_account.account(user_id?int,money?double)?engine=innodb;
另外,在本案例中,db_user庫和db_account庫是位于同一個mysql實例中的。?
? ? ? ?
案例代碼:
?? ?在使用了事務管理器之后,我們通過atomikos提供的UserTransaction接口的實現類com.atomikos.icatch.jta.UserTransactionImp來開啟、提交和回滾事務。而不再是使用java.sql.Connection中的setAutoCommit(false)的方式來開啟事務。其他JTA規范中定義的接口,開發人員并不需要直接使用。?
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要求為每個AtomikosDataSourceBean名稱,為了方便記憶,這里設置為和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?{?????????//?開啟事務?????????userTransaction.begin();??????????//?執行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);//?獲得自動生成的userId?????????}??????????//?模擬異常?,直接進入catch代碼塊,2個都不會提交//????????int?i=1/0;??????????//?執行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中添加以下依賴?
<dependency>????<groupId>org.springframework</groupId>????<artifactId>spring-jdbc</artifactId>????<version>4.3.7.RELEASE</version></dependency><dependency>????<groupId>org.springframework</groupId>????<artifactId>spring-context</artifactId>????<version>4.3.7.RELEASE</version></dependency><dependency>????<groupId>org.mybatis</groupId>????<artifactId>mybatis</artifactId>????<version>3.4.1</version></dependency><dependency>????<groupId>org.mybatis</groupId>????<artifactId>mybatis-spring</artifactId>????<version>1.3.1</version></dependency>
新建User實體
package?com.tianshouzhi.atomikos;public?class?User?{???private?int?id;???private?String?name;???//?setters?and?getters}
新建Account實例
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事務的bean,注意在使用jta事務的時候,依然可以使用spring的聲明式事務管理
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庫???@Autowired???private?AccountMapper?accountMapper;//操作db_account庫???@Transactional???public?void?insert()?{??????User?user?=?new?User();??????user.setName("wangxiaoxiao");??????userMapper.insert(user);?????????????//????int?i?=?1?/?0;//模擬異常,spring回滾后,db_user庫中user表中也不會插入記錄??????Account?account?=?new?Account();??????account.setUserId(user.getId());??????account.setMoney(123456789);??????accountMapper.insert(account);???}}
編寫配置文件spring-atomikos.xml
<?xml?version="1.0"?encoding="UTF-8"?><beans?xmlns="http://www.springframework.org/schema/beans"???????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">??????<!--==========針對兩個庫,各配置一個AtomikosDataSourceBean,底層都使用MysqlXADataSource=====================-->????<!--配置數據源db_user-->????<bean?id="db_user"?class="com.atomikos.jdbc.AtomikosDataSourceBean"??????????init-method="init"?destroy-method="close">????????<property?name="uniqueResourceName"?value="ds1"?/>????????<property?name="xaDataSourceClassName"??????????????????value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"?/>????????<property?name="xaProperties">????????????<props>????????????????<prop?key="url">jdbc:mysql://localhost:3306/db_user</prop>????????????????<prop?key="user">root</prop>????????????????<prop?key="password">shxx12151022</prop>????????????</props>????????</property>????</bean>?????<!--配置數據源db_account-->????<bean?id="db_account"?class="com.atomikos.jdbc.AtomikosDataSourceBean"??????????init-method="init"?destroy-method="close">????????<property?name="uniqueResourceName"?value="ds2"?/>????????<property?name="xaDataSourceClassName"??????????????????value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"?/>????????<property?name="xaProperties">????????????<props>????????????????<prop?key="url">jdbc:mysql://localhost:3306/db_account</prop>????????????????<prop?key="user">root</prop>????????????????<prop?key="password">shxx12151022</prop>????????????</props>????????</property>????</bean>?????<!--=============針對兩個數據源,各配置一個SqlSessionFactoryBean============?-->????<bean?id="ssf_user"?class="org.mybatis.spring.SqlSessionFactoryBean">????????<property?name="dataSource"?ref="db_user"?/>????</bean>?????<bean?id="ssf_account"?class="org.mybatis.spring.SqlSessionFactoryBean">????????<property?name="dataSource"?ref="db_account"?/>????</bean>?????<!--=============針對兩個SqlSessionFactoryBean,各配置一個MapperScannerConfigurer============?-->????<bean?class="org.mybatis.spring.mapper.MapperScannerConfigurer">????????<property?name="sqlSessionFactoryBeanName"?value="ssf_user"/>????????<!--指定com.tianshouzhi.atomikos.mappers.db_user包下的UserMapper接口使用ssf_user獲取底層數據庫連接-->????????<property?name="basePackage"?value="com.tianshouzhi.atomikos.mappers.db_user"/>????</bean>?????<bean?class="org.mybatis.spring.mapper.MapperScannerConfigurer">????????<property?name="sqlSessionFactoryBeanName"?value="ssf_account"/>????????<!--指定com.tianshouzhi.atomikos.mappers.ds_account包下的AccountMapper接口使用ssf_account獲取底層數據庫連接-->????????<property?name="basePackage"?value="com.tianshouzhi.atomikos.mappers.ds_account"/>????</bean>?????<!--================配置atomikos事務管理器========================-->????<bean?id="atomikosTransactionManager"?class="com.atomikos.icatch.jta.UserTransactionManager"?init-method="init"??????????destroy-method="close">????????<property?name="forceShutdown"?value="false"/>????</bean>?????<!--============配置spring的JtaTransactionManager,底層委派給atomikos進行處理===============-->????<bean?id="jtaTransactionManager"?class="org.springframework.transaction.jta.JtaTransactionManager">????????<property?name="transactionManager"?ref="atomikosTransactionManager"/>????</bean>?????<!--配置spring聲明式事務管理器-->????<tx:annotation-driven?transaction-manager="jtaTransactionManager"/>?????<bean?id="jtaService"?class="com.tianshouzhi.atomikos.JTAService"/></beans>
測試代碼
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();???}}
????建議讀者先直接按照上述代碼運行,以確定代碼執行后,db_user庫的user表和db_account的account表中的確各插入了一條記錄,以證明我們的代碼的確是操作了2個庫,屬于分布式事務。
?? ?然后將JTAService中的異常模擬的注釋打開,會發現出現異常后,兩個庫中都沒有新插入的數據庫,說明我們使用的JTA事務管理器的確保證數據的一致性了。
Atomikos配置
?? ?在掌握了Atomikos基本使用之后,我們對Atomikos的配置進行一下簡單的介紹。Atomikos在啟動后,默認會從以下幾個位置讀取配置文件,這里筆者直接貼出atomikos源碼進行說明:
com.atomikos.icatch.provider.imp.AssemblerImp#initializeProperties方法中定義了配置加載順序邏輯:?
@Override????public?ConfigProperties?initializeProperties()?{????????//讀取classpath下的默認配置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);????????//最終構造一個ConfigProperties對象,來表示實際要使用的配置????????Properties?finalProperties?=?new?Properties(customProperties);????????return?new?ConfigProperties(finalProperties);????}
配置文件優先級:transactions-defaults.properties<transactions.properties<jta.properties<自定義配置文件路徑,后面的配置會覆蓋之前同名key的配置。
其中transactions-defaults.properties是atomikos自帶的默認配置,位于transactions-xxx.jar中.
注意不同版本的默認配置可能不同。特別是3.x版本和4.x版本的差異比較明顯。 ??
以下是4.0.6中 transactions-default.properties中配置內容,筆者對這些配置進行了歸類,如下:?
===========================================================================??????????事務管理器(TM)配置參數???????=============================================================================#指定是否啟動磁盤日志,默認為true。在生產環境下一定要保證為true,否則數據的完整性無法保證com.atomikos.icatch.enable_logging=true#JTA/XA資源是否應該自動注冊com.atomikos.icatch.automatic_resource_registration=true#JTA事務的默認超時時間,默認為10000mscom.atomikos.icatch.default_jta_timeout=10000#事務的最大超時時間,默認為300000ms。這表示事務超時時間由?UserTransaction.setTransactionTimeout()較大者決定。4.x版本之后,指定為0的話則表示不設置超時時間com.atomikos.icatch.max_timeout=300000#指定在兩階段提交時,是否使用不同的線程(意味著并行)。3.7版本之后默認為false,更早的版本默認為true。如果為false,則提交將按照事務中訪問資源的順序進行。com.atomikos.icatch.threaded_2pc=false#指定最多可以同時運行的事務數量,默認值為50,負數表示沒有數量限制。在調用?UserTransaction.begin()方法時,可能會拋出一個”Max?number?of?active?transactions?reached”異常信息,表示超出最大事務數限制com.atomikos.icatch.max_actives=50#是否支持subtransaction,默認為truecom.atomikos.icatch.allow_subtransactions=true#指定在可能的情況下,否應該join?子事務(subtransactions),默認值為true。如果設置為false,對于有關聯的不同subtransactions,不會調用XAResource.start(TM_JOIN)com.atomikos.icatch.serial_jta_transactions=true#指定JVM關閉時是否強制(force)關閉事務管理器,默認為falsecom.atomikos.icatch.force_shutdown_on_vm_exit=false#在正常關閉(no-force)的情況下,應該等待事務執行完成的時間,默認為Long.MAX_VALUEcom.atomikos.icatch.default_max_wait_time_on_shutdown=9223372036854775807?========================================================================????????事務日志(Transaction?logs)記錄配置???????======================================================================#事務日志目錄,默認為./。com.atomikos.icatch.log_base_dir=./#事務日志文件前綴,默認為tmlog。事務日志存儲在文件中,文件名包含一個數字后綴,日志文件以.log為擴展名,如tmlog1.log。遇到checkpoint時,新的事務日志文件會被創建,數字增加。com.atomikos.icatch.log_base_name=tmlog#指定兩次checkpoint的時間間隔,默認為500com.atomikos.icatch.checkpoint_interval=500?========================================================================??????????事務日志恢復(Recovery)配置???????============================================================================#指定在多長時間后可以清空無法恢復的事務日志(orphaned),默認86400000mscom.atomikos.icatch.forget_orphaned_log_entries_delay=86400000#指定兩次恢復掃描之間的延遲時間。默認值為與com.atomikos.icatch.default_jta_timeout相同com.atomikos.icatch.recovery_delay=${com.atomikos.icatch.default_jta_timeout}#提交失敗時,再拋出一個異常之前,最多可以重試幾次,默認值為5com.atomikos.icatch.oltp_max_retries=5#提交失敗時,每次重試的時間間隔,默認10000mscom.atomikos.icatch.oltp_retry_interval=10000?========================================================================??????????其他???????===============================?=================================================================java.naming.factory.initial=com.sun.jndi.rmi.registry.RegistryContextFactorycom.atomikos.icatch.client_demarcation=falsejava.naming.provider.url=rmi://localhost:1099com.atomikos.icatch.rmi_export_class=nonecom.atomikos.icatch.trust_client_tm=false
當我們想對默認的配置進行修改時,可以在classpath下新建一個jta.properties,覆蓋同名的配置項即可。
關于不同版本配置的差異,請參考官方文檔:https://www.atomikos.com/Documentation/JtaProperties
打印日志
4.x版本之后,優先嘗試使用slf4j,如果沒有則嘗試使用log4j,如果二者都沒有,則使用JUL。
參見:https://www.atomikos.com/Documentation/ConfiguringTheLogs
注意這里是說的是如何配置打印工作日志(work log),而前面說的是事務日志(transactions log),二者不是不同的。
總結
以上是生活随笔為你收集整理的atomikos JTA/XA全局事务的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。