當前位置:
首頁 > 知識 > spring+atomikos+mytatis+jpa實現分散式事務管理實戰篇

spring+atomikos+mytatis+jpa實現分散式事務管理實戰篇

最近在工作中,由於業務的發展需求,需要搭建一個類似中台的項目(擁有獨立的資料庫),那麼跟我們項目中原本存在庫,一共兩個庫,而資料庫伺服器為MYSQL,這時候就涉及到分散式事務的管理了。我也在網上找了很多解決方案,但貌似aomikos+mybatis+jta的解決方案比較少,只能參考網上的案例來集成代碼到我們的項目當中,運行起來發現不定時拋出一些莫名其妙的異常,憑著自己的感覺一步步把這些坑給修補了,跑了兩天也沒看到異常拋出,程序也正常執行,註:我是在dubbo服務介面層以及spring quartz應用中都集成了atomikos+jta。下面來看一下集成的步驟

1)首先引入JAR包,我使用的是MAVEN來管理項目以及JAR包

<!-- transaction -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>atomikos-util</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jta</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jdbc</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-api</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
</dependency>
<!-- transaction -->

2)在你的項目class包中加入jta.properties配置文件,由於MAVEN JAVA項目,src/main/resources下面存放的在編譯時,會放到class里,所以我們放在

spring+atomikos+mytatis+jpa實現分散式事務管理實戰篇

而jta.properties的配置內容如下,按需調整吧

com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory
com.atomikos.icatch.console_file_name = ${log.dir}jta/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=ERROR
com.atomikos.icatch.enable_logging=false

配置完這個之後,下面就是數據源以及事務的配置了,數據源配置如下:

<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" />
<!--min-pool-size 最小連接數-->
<property name="minPoolSize" value="10" />
<!--max-pool-size 最大連接數 -->
<property name="maxPoolSize" value="30" />
<!--獲取連接失敗重新獲等待最大時間,在這個時間內如果有可用連接,將返回-->
<property name="borrowConnectionTimeout" value="60" />
<!-- 如果不設置這個值,Atomikos使用默認的300秒(即5分鐘),那麼在處理大批量數據讀取的時候,
一旦超過5分鐘,就會拋出類似 Resultset is close 的錯誤 -->
<property name="reapTimeout" value="20" />
<!-- max-idle-time 最大閑置時間,超過最小連接池連接的連接將將關閉 -->
<property name="maxIdleTime" value="60" />
<!-- maintenance-interval 連接回收時間 -->
<property name="maintenanceInterval" value="60" />
<!-- login-timeout java資料庫連接池,最大可等待獲取datasouce的時間 -->
<property name="loginTimeout" value="60" />
<property name="testQuery" value="SELECT 1" />
<!-- max-lifetime 連接最大存活時間 -->
<property name="maxLifetime" value="60"></property>
</bean>
<!-- WS數據源配置, 使用DBCP資料庫連接池 -->
<bean id="saleDataSource" parent="abstractXADataSource">
<property name="uniqueResourceName" value="saleDB" />
<property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
<property name="xaProperties">
<props>
<prop key="url">${jdbc.url}</prop>
<prop key="password">${jdbc.password}</prop>
<prop key="user">${jdbc.username}</prop>
<prop key="autoReconnect">true</prop>
<prop key="pinGlobalTxToPhysicalConnection">true</prop>
</props>
</property>
</bean>

<!-- 主數據源配置, 使用DBCP資料庫連接池 -->
<bean id="masterDataSource" parent="abstractXADataSource">
<property name="uniqueResourceName" value="masterDB" />
<property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
<property name="xaProperties">
<props>
<prop key="url">${csmjdbc.url}</prop>
<prop key="password">${jdbc.password}</prop>
<prop key="user">${jdbc.username}</prop>
<prop key="autoReconnect">true</prop>
<prop key="pinGlobalTxToPhysicalConnection">true</prop>
</props>
</property>
</bean>

<!-- 主庫MyBatis配置 -->
<bean id="sqlSessionFactoryMain" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="masterDataSource" />
<!-- 自動掃描entity目錄, 省掉Configuration.xml里的手工配置 -->
<property name="typeAliasesPackage" value="${type.aliases.package:com.csair}" />
<!-- 顯式指定Mapper文件位置 -->
<property name="mapperLocations" value="classpath*:/main/dao/mapper/**/*.xml" />
<property name="typeHandlersPackage"
value="${type.handlers.package:com.csair.diamond.repository.mybatis.handler}" />
<property name="configLocation" value="classpath:/META-INF/mybatis-config.xml" />
</bean>

<!-- WS庫的MyBatis配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="saleDataSource" />
<!-- 自動掃描entity目錄, 省掉Configuration.xml里的手工配置 -->
<property name="typeAliasesPackage" value="${type.aliases.package:com.csair}" />
<!-- 顯式指定Mapper文件位置 -->
<property name="mapperLocations" value="classpath*:/dao/mapper/**/*.xml" />
<property name="typeHandlersPackage"
value="${type.handlers.package:com.csair.diamond.repository.mybatis.handler}" />
<property name="plugins">
<list>
<ref bean="mybatisSqlInjectionHandlerInterceptor" />
<ref bean="mybatisStatementHandlerInterceptor" />
<ref bean="mybatisResultSetHandlerInterceptor" />
</list>
</property>
<property name="configLocation" value="classpath:/META-INF/mybatis-config.xml" />
</bean>
<bean id="mybatisStatementHandlerInterceptor"
class="com.csair.diamond.repository.mybatis.interceptor.StatementHandlerInterceptor">
<property name="dialectClass"
value="com.csair.diamond.repository.mybatis.MySqlDialect" />
</bean>
<bean id="mybatisResultSetHandlerInterceptor"
class="com.csair.diamond.repository.mybatis.interceptor.ResultSetHandlerInterceptor">
</bean>
<bean id="mybatisSqlInjectionHandlerInterceptor"
class="com.csair.diamond.repository.mybatis.interceptor.SqlInjectionHandlerInterceptor">
</bean>
<!-- 掃描basePackage下所有以@Repository標識的 介面 -->
<bean name = "mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage"
value="${mapper.scanner.base.package:com.csair.csm.dao}" />
<property name="annotationClass"
value="com.csair.diamond.repository.annotation.Repository" />
<!-- <property name="sqlSessionFactory" ref="sqlSessionFactory" /> -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>

<bean name = "mainMapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage"
value="com.csair.csm.main.dao" />
<property name="annotationClass"
value="com.csair.diamond.repository.annotation.Repository" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryMain" />
</bean>

事務的配置如下

<!-- atomikos事務管理器 -->
<bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close">
<!-- close()時是否強制終止事務 -->
<property name="forceShutdown">
<value>true</value>
</property>
</bean>

<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
<property name="transactionTimeout" value="300" />
</bean>
<!-- spring 事務管理器 -->
<bean id="springTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager" ref="atomikosTransactionManager" />
<property name="userTransaction" ref="atomikosUserTransaction" />
<!-- 必須設置,否則程序出現異常 JtaTransactionManager does not support custom isolation levels by default -->
<property name="allowCustomIsolationLevels" value="true"/>
</bean>
<tx:annotation-driven transaction-manager="springTransactionManager" proxy-target-class="true" />

spring-quartz配置文件如下:

<bean id="quartzSchedulerSupplier"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource">
<ref bean="saleDataSource" />
</property>
<property name="applicationContextSchedulerContextKey" value="applicationContextKey" />
<property name="configLocation" value="classpath:quartz.properties" />
<property name="triggers">
<list>
<ref bean="triggerSupplier" />
<ref bean="triggerProduct" />
<ref bean="triggerPicture" />
</list>
</property>
</bean>

我是使用事務的註解形式回滾事務,我原本是採用切入點來定義某個包下面的所有類以哪些方法開頭是有事務並且回滾,哪些是沒有事務的,但是集成spring quartz會發現事務開啟不一致的問題,spring quartz也有自己的數據源,一套自己的事務,如果不用事務註解,定時任務會開啟自己的本地事務,而定時任務調用服務又是一個XA的事務,這時候就會拋事務不統一。

Caused by: com.atomikos.datasource.ResourceException: XA resource "mysql/first": resume for XID "3137322E31362E33312E38332E746D30303030313030303131:3137322E31362E33312E38332E746D31" raised -9: the XA resource is currently involved in a local (non-XA) transaction

解決方案:採用事務註解,並且在定時任務入口類標註上註解。

如果發現以下這種錯誤異常問題,主要是系統有兩個事務,一個事務提交,一個事務未提交,導致事務衝突,建議採用事務註解在最外層方法標註上。

Cannot call method "commit" while a global transaction is runing(因為我是繼承了定時任務,在方法內部手動開啟事務,當我方法內部事務還未提交時,定時任務自己內部的事務就提交了,這時候就會拋這個異常,如果集成定時任務,建議不要手動開啟事務)

spring+atomikos+mytatis+jpa實現分散式事務管理實戰篇

如果在運行期拋[ERROR][2016-11-03 10:17:30,771][com.atomikos.recovery.imp.CachedRepository]Corrupted log file - restart JVM

com.atomikos.recovery.LogReadException: java.lang.ArrayIndexOutOfBoundsException: 1 at ,解決方案就是把atomikos的jar包版本升級到4.0.4即可。

如果在運行期發現一開始是正常,後期頻繁拋如下的錯誤,根據我的填坑經驗,這個是資料庫連接池選型不對的問題,以及配置連接池屬性跟資料庫的配置不一致。

2019-05-06 17:57:37.865 [ Atomikos:2 ] - [ WARN ] [com.atomikos.recovery.xa.XaResourceRecoveryManager : 40] - Error while retrieving xids from resource - will retry later...

com.mysql.jdbc.jdbc2.optional.MysqlXAException: No operations allowed after connection closed.

at com.mysql.jdbc.jdbc2.optional.MysqlXAConnection.mapXAExceptionFromSQLException(MysqlXAConnection.java:608)

at com.mysql.jdbc.jdbc2.optional.MysqlXAConnection.recover(MysqlXAConnection.java:335)

at com.mysql.jdbc.jdbc2.optional.MysqlXAConnection.recover(MysqlXAConnection.java:255)

at com.atomikos.datasource.xa.RecoveryScan.recoverXids(RecoveryScan.java:32)

at com.atomikos.recovery.xa.XaResourceRecoveryManager.retrievePreparedXidsFromXaResource(XaResourceRecoveryManager.java:158)

at com.atomikos.recovery.xa.XaResourceRecoveryManager.recover(XaResourceRecoveryManager.java:67)

at com.atomikos.datasource.xa.XATransactionalResource.recover(XATransactionalResource.java:451)

at com.atomikos.icatch.imp.TransactionServiceImp.performRecovery(TransactionServiceImp.java:490)

at com.atomikos.icatch.imp.TransactionServiceImp.access$000(TransactionServiceImp.java:56)

at com.atomikos.icatch.imp.TransactionServiceImp$1.alarm(TransactionServiceImp.java:471)

at com.atomikos.timing.PooledAlarmTimer.notifyListeners(PooledAlarmTimer.java:95)

at com.atomikos.timing.PooledAlarmTimer.run(PooledAlarmTimer.java:82)

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)

at java.lang.Thread.run(Thread.java:748)

一開始xaDataSourceClassName屬性值我是採用com.alibaba.druid.pool.xa.DruidXADataSource

<bean id="abstractXADataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean"
init-method="init" destroy-method="close" abstract="true">
<property name="xaDataSourceClassName" value="com.alibaba.druid.pool.xa.DruidXADataSource" />
<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" />
</bean>

後來採用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" />
<!--min-pool-size 最小連接數-->
<property name="minPoolSize" value="10" />
<!--max-pool-size 最大連接數 -->
<property name="maxPoolSize" value="30" />
<!--獲取連接失敗重新獲等待最大時間,在這個時間內如果有可用連接,將返回-->
<property name="borrowConnectionTimeout" value="60" />
<!-- 如果不設置這個值,Atomikos使用默認的300秒(即5分鐘),那麼在處理大批量數據讀取的時候,
一旦超過5分鐘,就會拋出類似 Resultset is close 的錯誤 -->
<property name="reapTimeout" value="20" />
<!-- max-idle-time 最大閑置時間,超過最小連接池連接的連接將將關閉 -->
<property name="maxIdleTime" value="60" />
<!-- maintenance-interval 連接回收時間 -->
<property name="maintenanceInterval" value="60" />
<!-- login-timeout java資料庫連接池,最大可等待獲取datasouce的時間 -->
<property name="loginTimeout" value="60" />
<property name="testQuery" value="SELECT 1" />
<!-- max-lifetime 連接最大存活時間 -->
<property name="maxLifetime" value="60"></property>
</bean>

作者:huangkejie

原文:https://my.oschina.net/u/3155476/blog/3047302

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員小新人學習 的精彩文章:

支撐百萬並發的資料庫架構如何設計?
現代IM系統中的消息系統架構 - 架構篇

TAG:程序員小新人學習 |