一、概述
1、業務背景
對老系統進行重構合並,導致新系統需要同時對3個數據庫進行管理。由於出現跨庫業務,需要實現分布式事務。
2、開發環境
spring框架版本 4.3.10.RELEASE
持久層為結合mybatis寫的領域模型,如
每一個entity對應數據庫的一張表,@DataSource注解(自定義)了對應數據源的key值。所以一個業務中可能存在數據源的切換。
事務采用注解@Transaction驅動。
二、spring對多數據源的支持
spring框架通過抽象類AbstractRoutingDataSource來實現多數據源支持。
AbstractRoutingDataSource中有一個抽象方法determineCurrentLookupKey()。子類實現該方法即可。
舉例如下:
package com.cmcc.cq.xx.common.mybatis; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return getDataSourceType(); } private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); public static void setDataSourceType(String dataSourceType){ contextHolder.set(dataSourceType); } public static String getDataSourceType(){ return contextHolder.get(); } public static void clearDataSourceType(){ contextHolder.remove(); } }
ThreadLocal保存當前線程的數據源,執行數據庫操作之前會通過解析注解@DataSource設置下次操作的數據源類型,即相應的key值(見后文數據源配置)。
到此,在不使用事務的情況下,可以完成多數據源切換。
三、spring分布式事務支持
1、DataSourceTransactionManager不支持多數據源
DataSourceTransactionManager事務管理器是一個單數據源事務管理器,在實例化時候會注入一個數據源。
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dynamicDataSource"> </property>
</bean>
注意:雖然我們注入的是一個動態數據源,但是DataSourceTransactionManager不會因為我們使用動態數據源而跟着變化。
因為DataSourceTransactionManager開始事務時獲取數據庫connection時並不是每次都通過dataSource來獲取,一般都是通過ConnectionHolder類獲得。
所以開啟事務會報找不到表或視圖的異常。
同時DataSourceTransactionManager也不能實現跨庫事務的一致性。
2、JtaTransactionManager支持分布式事務
JtaTransactionManager事務管理器只是提供一個接入方式,並沒有做相應的實現,目前比較流行的分布式事務第三方實現是atomikos和jotm。其中jotm更新日期是2010年,建議使用atomikos。
兩者配置也是大同小異,以atomikos為例。
引入maven依賴
<dependency> <groupId>com.atomikos</groupId> <artifactId>transactions-jdbc</artifactId> <version>3.7.0</version> </dependency>
首先是數據源的配置,必須要使用類com.atomikos.jdbc.AtomikosDataSourceBean,不能再使用其他的連接池替代。xaDataSourceClassName要根據數據庫選擇相應的類,在數據庫對應的驅動包里可以找到。
<bean id="dataSource3" class="com.atomikos.jdbc.AtomikosDataSourceBean" destroy-method="close">
<property name="uniqueResourceName" value="key值"/>
<property name="xaDataSourceClassName" value="oracle.jdbc.xa.client.OracleXADataSource"/>
<property name="xaProperties">
<props>
<prop key="URL">${jdbc.dataSource.url3}</prop>
<prop key="user">${jdbc.dataSource.username3}</prop>
<prop key="password">${jdbc.dataSource.password3}</prop>
</props>
</property>
<property name="minPoolSize" value="${jdbc.dataSource.minimumIdle3}" />
<property name="maxPoolSize" value="${jdbc.dataSource.maximumPoolSize3}" />
<property name="borrowConnectionTimeout" value="${jdbc.dataSource.connectionTimeout3}" />
<property name="testQuery" value="${jdbc.dataSource.connectionTestQuery3}" />
<property name="maintenanceInterval" value="60" />
<property name="maxIdleTime" value="${jdbc.dataSource.idleTimeout3}"></property>
</bean>
然后是JtaTransactionManager事務管理器配置。
JtaTransactionManager有兩個重要屬性需要配置transactionManager和userTransaction。對應的配置類如下:
注:在jotm中只需配置transactionManager屬性
最后是atomikos的配置文件jta.properties。
配置路徑:根路徑
jta.properties也可命名為transactions.properties,如果不配置也可以啟動項目,因為幾乎所有配置項都有默認值。
transactions.properties配置