2017年8月16日11:45:39更新:這裏貼出我本身寫的一個demo,放在CSDN上了。http://download.csdn.net/download/llzhaoyun/9912658java
前面寫了一篇關於使用Spring + druid + Mybatis配置多數據源,並在代碼中動態選擇使用不一樣的數據源的博文,當時寫該文時,因爲比較忙,只簡單作了記錄,後來又作了一些擴展,添加了分佈式事務等,今天就另起一文,詳細記錄下目前生產線上正在使用的穩定版本。mysql
業務場景:web
有一個系統A做爲數據中轉服務器。若干個系統數據都須要通過系統A作處理、轉發,而數據交互有多種方式,如WebService、REST API、中間表等,A也會操做本身的數據庫。本文主要圍繞操做中間表的方式來說。spring
A須要操做的本身的數據庫是肯定的,但因爲接入的系統可能隨業務發展而增長或改變,因此不能將中間表的鏈接信息寫死,須要使用配置文件維護,並在服務器啓動的時候動態加載,而後由不一樣的業務去決定使用哪個數據源。(該段文本描述的業務問題已在上篇文章https://my.oschina.net/simpleton/blog/868608中解決)sql
因爲系統A會讀中間表,而後將讀取的數據預處理,而後存入A系統的數據庫或其餘中間庫,若是使用DataSourceTransactionManager做爲事務管理器來控制事務,會出現多數據庫事務不一致的問題,那麼,解決這類問題有兩種方案:編程式事務和分佈式事務。若是使用編程式事務的話,須要在每一個地方顯示地管理實務(開啓、提交、回滾),耦合較高。對於複雜的業務來講,很差處理,故不適合系統A的業務場景。後來看了分佈式事務框架atomikos,以爲應該能解決我目前的問題,經測試,效果很贊,下面就讓咱們一塊兒來看看它是什麼集成到系統的。(原理的話,你們本身百度一下)數據庫
好了,說了這麼多,下面來一步一步解決咱們問題。apache
1、配置2套數據源(defaultDataSource:管理A系統本身數據庫鏈接的數據源。atomikosDynamicDataSource:管理數據源動態初始化、選擇、銷燬的數據源)。編程
<?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-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd "> <!-- 默認的dataSource --> <bean id="defaultDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" destroy-method="close"> <!-- uniqueResourceName很重要,不能重複 --> <property name="uniqueResourceName" value="defaultDataSource"/> <property name="xaDataSourceClassName" value="com.alibaba.druid.pool.xa.DruidXADataSource" /> <property name="borrowConnectionTimeout" value="${pro.borrowConnectionTimeout}" /> <property name="minPoolSize" value="${pro.minPoolSize}" /> <property name="maxPoolSize" value="${pro.maxPoolSize}" /> <property name="maxLifetime" value="0" /> <!-- xaProperties實質上是設置屬性xaDataSourceClassName的對象的值,即com.alibaba.druid.pool.xa.DruidXADataSource的參數值 --> <property name="xaProperties"> <props> <prop key="driverClassName">${pro.driver}</prop> <prop key="url">${pro.url}</prop> <prop key="username">${pro.username}</prop> <prop key="password">${pro.password}</prop> <prop key="initialSize">${pro.initialSize}</prop> <prop key="minIdle">${pro.minIdle}</prop> <prop key="maxActive">${pro.maxActive}</prop> <prop key="maxWait">${pro.maxWait}</prop> <prop key="timeBetweenEvictionRunsMillis">${pro.timeBetweenEvictionRunsMillis}</prop> <prop key="minEvictableIdleTimeMillis">${pro.minEvictableIdleTimeMillis}</prop> <prop key="validationQuery">SELECT 1</prop> <prop key="testWhileIdle">true</prop> <prop key="testOnBorrow">false</prop> <prop key="testOnReturn">false</prop> <prop key="poolPreparedStatements">true</prop> <prop key="maxPoolPreparedStatementPerConnectionSize">${pro.maxPoolPreparedStatementPerConnectionSize}</prop> <prop key="filters">stat</prop> </props> </property> </bean> <!-- 定義主業務使用的sqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation"> <value>classpath:config/sqlMapConfig.xml</value> </property> <property name="dataSource" ref="defaultDataSource" /> <property name="typeAliasesPackage" value="com.eya.model.domain" /> <!-- 掃描defaultDataSource對應的數據源的SQL配置文件,這部分數據庫操做就不須要動態切換數據源,直接使用默認的數據源操做數據庫便可 --> <property name="mapperLocations" value="classpath:com/eya/dao/**/*.xml" /> </bean> <!-- 掃描mybatis的接口所在的文件 --> <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.eya.dao,com.eya.pubmapper" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> <!-- 定義中間表使用的數據源、sqlSessionFactory等 --> <bean id="atomikosDynamicDataSource" class="com.eya.pubservice.datasource.AtomikosDynamicDataSource" > <!-- <property name="defaultTargetDataSource" ref="defaultDataSource"></property> --> <property name="targetDataSources"> <map> <!-- 這裏還能夠加多個數據源,因爲不肯定數據源的地址信息,因此這裏不配置,在代碼中去動態初始化 --> </map> </property> </bean> <!-- 定義數據交互業務使用的sqlSessionFactory --> <bean id="sqlSessionFactory4MiddleTable" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="atomikosDynamicDataSource" /> <!-- 掃描中間表的SQL配置文件 --> <property name="mapperLocations" value="classpath:com/eya/middletable/**/*.xml" /> </bean> <!-- 掃描mybatis的接口所在的文件,注意這裏掃描的mybatis的xml文件和主業務的xml文件不一樣 --> <bean id="mapperScannerConfigurer4MiddleTable" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.eya.middletable" /> <!-- 這裏要使用sqlSessionFactoryBeanName屬性,不然會報錯 --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory4MiddleTable"/> </bean> <!-- jta分佈式事務 --> <!-- Atomikos 事務管理器配置 --> <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close"> <!-- <property name="startupTransactionService" value="false" /> --> <!-- close()時是否強制終止事務 --> <property name="forceShutdown" value="false" /> </bean> <!-- Atomikos UserTransaction配置 --> <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp"></bean> <!-- JTA事務管理器 --> <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManager"> <ref bean="atomikosTransactionManager" /> </property> <property name="userTransaction"> <ref bean="atomikosUserTransaction" /> </property> </bean> <!-- 開啓註解事務,使用@Transactional來標記須要事務控制的方法或類 --> <tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" /> </beans>
說明一下,這是spring的配置文件,主要配置mybatis的信息,其中涉及到了數據源、mybatis的SQL文件和接口類、聲明事務管理。這裏面有幾個須要重點指出的地方緩存
1.文件中的表達式,如${pro.url},是引入properties的內容。服務器
2.DataSource的定義:和原來定義DataSource不一樣,原來定義時,是使用class爲com.alibaba.druid.pool.xa.DruidXADataSource(也能夠是c3p0)來定義bean,而後配置數據源的信息,如今是定義class爲com.atomikos.jdbc.AtomikosDataSourceBean的bean,而後使用xaDataSourceClassName指明具體使用的數據源類,使用xaProperties來配置數據源的屬性。
3.defaultDataSource:defaultDataSource爲系統A本身的數據庫信息,這個是肯定的,因此直接在配置文件中定義好,而後根據defaultDataSource來配置sqlSessionFactory,並使用sqlSessionFactory來配置mapperScannerConfigurer,這部分是完成系統A本身的數據源配置以及myBatis配置(SQL文件掃描和接口類)。注意:定義org.mybatis.spring.mapper.MapperScannerConfigurer的bean時,須要使用sqlSessionFactoryBeanName屬性來指定使用哪個sqlSessionFactory,否則會拋出異常。
4.atomikosDynamicDataSource:這是一個本身實現的數據源類(代碼我稍後貼出來並講解代碼中的內容),屬性targetDataSources是一個map集合,用於存儲不一樣的數據源信息。以上配置文件中,沒有配置map的值,由於不能像defaultDataSource同樣肯定數據庫鏈接地址等信息,因此在服務器啓動的時候,用代碼去初始化不一樣的數據源信息:根據配置的多個數據庫信息,初始化對應個數的數據源,而後存放到targetDataSources中。targetDataSources被初始化後,在系統運行過程當中,根據不一樣的業務,從targetDataSources中選擇不一樣的數據源來操做數據庫。
5.分佈式事務配置:在配置文件中定義了atomikosTransactionManager、atomikosUserTransaction兩個bean,而後在txManager中注入這2個bean,並在最後使用<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" />開啓註解事務。使用過程當中,出現了事務提交超時的問題(com.atomikos.icatch.RollbackException: Prepare: NO vote),解決方案是添加一個atomikos配置文件,配置事務的相關屬性,具體內容參考個人博文:https://my.oschina.net/simpleton/blog/915683
說明:大部分狀況下,按照上述配置,在配置 atomikosDynamicDataSource的時候,同時配置targetDataSources的值,並在代碼中動態肯定使用哪套數據源(繼承AbstractRoutingDataSource並實現determineCurrentLookupKey便可)就能夠了,這也是網上目前大部分資料已經完成的內容。
2、建立AbstractDynamicDataSource、AtomikosDynamicDataSource,管理動態建立的數據源,而後建立一個標記使用的數據源的上下文對象:DBContextHolder,用來標記當前業務須要使用哪個數據源。
package com.eya.pubservice.datasource; import java.util.Map; import javax.sql.DataSource; import org.apache.commons.collections.MapUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * 管理動態數據源的數據源父類 * @create ll * @createDate 2017年3月27日 下午2:38:05 * @update * @updateDate */ public abstract class AbstractDynamicDataSource<T extends DataSource> extends AbstractRoutingDataSource implements ApplicationContextAware { /** 日誌 */ protected Logger logger = LoggerFactory.getLogger(getClass()); /** 默認的數據源KEY */ //protected static final String DEFAULT_DATASOURCE_KEY = "defaultDataSource"; /** 數據源KEY-VALUE鍵值對 */ private Map<Object, Object> targetDataSources; /** spring容器上下文 */ private static ApplicationContext ctx; public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ctx = applicationContext; } public static ApplicationContext getApplicationContext() { return ctx; } public static Object getBean(String name) { return ctx.getBean(name); } /** * @param targetDataSources the targetDataSources to set */ public void setTargetDataSources(Map<Object, Object> targetDataSources) { this.targetDataSources = targetDataSources; super.setTargetDataSources(targetDataSources); // afterPropertiesSet()方法調用時用來將targetDataSources的屬性寫入resolvedDataSources中的 super.afterPropertiesSet(); } /** * @return the targetDataSources */ public Map<Object, Object> getTargetDataSources() { return this.targetDataSources; } /** * 建立數據源 * @param driverClassName 數據庫驅動名稱 * @param url 鏈接地址 * @param username 用戶名 * @param password 密碼 * @return 數據源{@link T} * @Author : ll. create at 2017年3月27日 下午2:44:34 */ public abstract T createDataSource(String driverClassName, String url, String username, String password); /** * 設置系統當前使用的數據源 * <p>數據源爲空或者爲0時,自動切換至默認數據源,即在配置文件中定義的默認數據源 * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey() */ @Override protected Object determineCurrentLookupKey() { logger.debug("【設置系統當前使用的數據源】"); Map<String, Object> configMap = DBContextHolder.getDBType(); logger.debug("【當前數據源配置爲:{}】", configMap); if (MapUtils.isEmpty(configMap)) { // 使用默認數據源 //return DEFAULT_DATASOURCE_KEY; throw new IllegalArgumentException("沒有指定數據源"); } // 判斷數據源是否須要初始化 this.verifyAndInitDataSource(); logger.debug("【切換至數據源:{}】", configMap); return configMap.get(DBContextHolder.DATASOURCE_KEY); } /** * 判斷數據源是否須要初始化 * @Author : ll. create at 2017年3月27日 下午3:57:43 */ private void verifyAndInitDataSource() { Map<String, Object> configMap = DBContextHolder.getDBType(); Object obj = this.targetDataSources.get(configMap.get(DBContextHolder.DATASOURCE_KEY)); if (obj != null) { return; } logger.info("【初始化數據源】"); T datasource = this.createDataSource(configMap.get(DBContextHolder.DATASOURCE_DRIVER) .toString(), configMap.get(DBContextHolder.DATASOURCE_URL).toString(), configMap.get(DBContextHolder.DATASOURCE_USERNAME).toString(), configMap.get(DBContextHolder.DATASOURCE_PASSWORD).toString()); this.addTargetDataSource(configMap.get(DBContextHolder.DATASOURCE_KEY).toString(), datasource); } /** * 往數據源key-value鍵值對集合添加新的數據源 * @param key 新的數據源鍵 * @param dataSource 新的數據源 * @Author : ll. create at 2017年3月27日 下午2:56:49 */ public void addTargetDataSource(String key, T dataSource) { this.targetDataSources.put(key, dataSource); super.setTargetDataSources(this.targetDataSources); // afterPropertiesSet()方法調用時用來將targetDataSources的屬性寫入resolvedDataSources中的 super.afterPropertiesSet(); } }
package com.eya.pubservice.datasource; import java.sql.SQLException; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import com.alibaba.druid.filter.Filter; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.xa.DruidXADataSource; import com.atomikos.jdbc.AtomikosDataSourceBean; import com.eya.pubservice.bean.properties.DBProperties; import com.eya.util.ProIdUtil; /** * * @create ll * @createDate 2017年3月31日 下午2:14:27 * @update * @updateDate */ public class AtomikosDynamicDataSource extends AbstractDynamicDataSource<AtomikosDataSourceBean> { /** db.properties配置信息的對應文件 */ @Autowired private DBProperties dBProperties; private boolean testWhileIdle = true; private boolean testOnBorrow = false; private boolean testOnReturn = false; // 是否打開鏈接泄露自動檢測 private boolean removeAbandoned = false; // 鏈接長時間沒有使用,被認爲發生泄露時長 private long removeAbandonedTimeoutMillis = 300 * 1000; // 發生泄露時是否須要輸出 log,建議在開啓鏈接泄露檢測時開啓,方便排錯 private boolean logAbandoned = false; // 只要maxPoolPreparedStatementPerConnectionSize>0,poolPreparedStatements就會被自動設定爲true,使用oracle時能夠設定此值。 // private int maxPoolPreparedStatementPerConnectionSize = -1; // 配置監控統計攔截的filters private String filters; // 監控統計:"stat" 防SQL注入:"wall" 組合使用: "stat,wall" private List<Filter> filterList; /* * 建立數據源,這裏建立的數據源是帶鏈接池信息的 * @see com.eya.pubservice.datasource.IDynamicDataSource#createDataSource(java.lang.String, java.lang.String, java.lang.String, java.lang.String) */ @Override public AtomikosDataSourceBean createDataSource(String driverClassName, String url, String username, String password) { DruidXADataSource ds = new DruidXADataSource(); ds.setName(ProIdUtil.get16TimeRandom()); ds.setUrl(url); ds.setUsername(username); ds.setPassword(password); ds.setDriverClassName(driverClassName); ds.setInitialSize(dBProperties.getInitialSize()); ds.setMinIdle(dBProperties.getMinIdle()); ds.setMaxActive(dBProperties.getMaxActive()); ds.setMaxWait(dBProperties.getMaxWait()); // ds.setTimeBetweenConnectErrorMillis(dBProperties.getTimeBetweenConnectErrorMillis()); ds.setTimeBetweenEvictionRunsMillis(dBProperties.getTimeBetweenEvictionRunsMillis()); ds.setMinEvictableIdleTimeMillis(dBProperties.getMinEvictableIdleTimeMillis()); // ds.setValidationQuery(dBProperties.getValidationQuery()); ds.setTestWhileIdle(testWhileIdle); ds.setTestOnBorrow(testOnBorrow); ds.setTestOnReturn(testOnReturn); ds.setRemoveAbandoned(removeAbandoned); ds.setRemoveAbandonedTimeoutMillis(removeAbandonedTimeoutMillis); ds.setLogAbandoned(logAbandoned); // 只要maxPoolPreparedStatementPerConnectionSize>0,poolPreparedStatements就會被自動設定爲true,參照druid的源碼 ds.setMaxPoolPreparedStatementPerConnectionSize( dBProperties.getMaxPoolPreparedStatementPerConnectionSize()); if (StringUtils.isNotBlank(filters)) try { ds.setFilters(filters); } catch (SQLException e) { ds.close(); throw new RuntimeException(e); } AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean(); atomikosDataSourceBean.setXaDataSource(ds); // 設置數據源的惟一名稱,不容許重複,按照本身的須要,生成一個不重複的值便可 atomikosDataSourceBean.setUniqueResourceName(ProIdUtil.getTimeRandomId(32)); atomikosDataSourceBean.setMaxPoolSize(dBProperties.getMaxPoolSize()); atomikosDataSourceBean.setMinPoolSize(dBProperties.getMinPoolSize()); atomikosDataSourceBean .setBorrowConnectionTimeout(dBProperties.getBorrowConnectionTimeout()); // atomikosDataSourceBean.getXaProperties().setProperty("", arg1); // atomikosDataSourceBean.getXaProperties().setProperty("", arg1); // atomikosDataSourceBean.getXaProperties().setProperty("", arg1); // atomikosDataSourceBean.getXaProperties().setProperty("", arg1); atomikosDataSourceBean.setMaxLifetime(0); addFilterList(ds); return atomikosDataSourceBean; } private void addFilterList(DruidDataSource ds) { if (filterList != null) { List<Filter> targetList = ds.getProxyFilters(); for (Filter add : filterList) { boolean found = false; for (Filter target : targetList) { if (add.getClass().equals(target.getClass())) { found = true; break; } } if (!found) targetList.add(add); } } } }
package com.eya.pubservice.datasource; import java.util.HashMap; import java.util.Map; /** * 當前正在使用的數據源信息的線程上線文 * @create ll * @createDate 2017年3月27日 下午2:37:07 * @update * @updateDate */ public class DBContextHolder { /** 數據源的KEY */ public static final String DATASOURCE_KEY = "DATASOURCE_KEY"; /** 數據源的URL */ public static final String DATASOURCE_URL = "DATASOURCE_URL"; /** 數據源的驅動 */ public static final String DATASOURCE_DRIVER = "DATASOURCE_DRIVER"; /** 數據源的用戶名 */ public static final String DATASOURCE_USERNAME = "DATASOURCE_USERNAME"; /** 數據源的密碼 */ public static final String DATASOURCE_PASSWORD = "DATASOURCE_PASSWORD"; private static final ThreadLocal<Map<String, Object>> contextHolder = new ThreadLocal<Map<String, Object>>(); public static void setDBType(Map<String, Object> dataSourceConfigMap) { contextHolder.set(dataSourceConfigMap); } public static Map<String, Object> getDBType() { Map<String, Object> dataSourceConfigMap = contextHolder.get(); if (dataSourceConfigMap == null) { dataSourceConfigMap = new HashMap<String, Object>(); } return dataSourceConfigMap; } public static void clearDBType() { contextHolder.remove(); } }
說明一下:
1.AbstractDynamicDataSource:該類繼承AbstractRoutingDataSource,並重寫determineCurrentLookupKey方法,用於肯定使用哪一個數據源。該類又實現了ApplicationContextAware,在Spring容器初始化的時候,會執行setApplicationContext方法,將Spring容器的上下文對象注入到屬性ctx中,而後就可使用ctx.getBean(name)來獲取Spring容器中的對象。
2.AtomikosDynamicDataSource:基於atomikos的數據源類,其實我還有一個DruidDynamicDataSource類,這裏沒有給出,內容大部分同樣,只是createDataSource的實現內容不同,DruidDynamicDataSource只是簡單地建立了一個基於Druid的數據源,而AtomikosDynamicDataSource是建立的一個可加入atomikos分佈式事務的數據源。
3.DBProperties是一個基於Spring注入來獲取properties內容添加的方式添加的一個和數據庫配置文件(db.properties)內容相同的JAVA類(參考https://my.oschina.net/simpleton/blog/489129),用於獲取 數據庫配置文件(db.properties)的配置信息。我這裏就不貼出該類的代碼,你們能夠本身想辦法獲取配置文件信息或直接寫死均可以,畢竟這裏不是本文的重點。
3、服務啓動,根據配置文件,初始化數據源。MiddleTableService.java
package com.eya.pubservice.datasource; import java.util.HashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.atomikos.jdbc.AtomikosDataSourceBean; import com.eya.common.exception.BusinessException; import com.eya.pubservice.InterfaceConfigService; import com.eya.pubservice.bean.interfaceConfig.InterfaceConfigMtDetail; import com.eya.util.ProCollection; import com.eya.util.ProString; /** * 中間表服務類 * @create ll * @createDate 2017年3月28日 上午9:36:45 * @update * @updateDate */ @Component public class MiddleTableService { /** 日誌 */ private static final Logger LOGGER = LoggerFactory.getLogger(MiddleTableService.class); /** 接口配置服務類 */ @Autowired private InterfaceConfigService interfaceConfigService; /** Atomikos數據源 */ @Autowired private AtomikosDynamicDataSource atomikosDynamicDataSource; /** * 隨服務器啓動,根據機構中間表信息配置,初始化數據源 * @Author : ll. create at 2017年3月28日 上午9:37:19 */ public synchronized void init() { LOGGER.info("【根據機構中間表信息配置,初始化數據源 】"); // 清空全部數據源 atomikosDynamicDataSource.getTargetDataSources().clear(); // 這段代碼是獲取全部的中間表數據庫配置信息,讀者不用關心,按照本身的方式獲取配置便可 List<InterfaceConfigMtDetail> mtDetails = interfaceConfigService.getMtDetails(); if (ProCollection.isEmpty(mtDetails)) { LOGGER.info("【無中間表配置數據,終止中間表數據源初始化任務】"); return; } for (InterfaceConfigMtDetail interfaceConfigMtDetail : mtDetails) { // 判斷數據源是否已經被初始化 if (atomikosDynamicDataSource.getTargetDataSources().containsKey( interfaceConfigMtDetail.getDataSourceKey())) { // 已經初始化 continue; } // 建立數據源 AtomikosDataSourceBean dataSource = atomikosDynamicDataSource.createDataSource( interfaceConfigMtDetail.getDriver(), interfaceConfigMtDetail.getUrl(), interfaceConfigMtDetail.getUsername(), interfaceConfigMtDetail.getPassword()); // 添加到targetDataSource中,緩存起來 atomikosDynamicDataSource.addTargetDataSource( interfaceConfigMtDetail.getDataSourceKey(), dataSource); } } /** * 數據源控制開關,用於指定數據源 * @param interfaceCode 接口的惟一嗎 * @Author : ll. create at 2017年3月28日 下午1:56:28 */ public void dataSourceSwitch(String interfaceCode) { InterfaceConfigMtDetail mtDetail = interfaceConfigService.getMtDetailMap().get( interfaceCode); if (mtDetail == null) { throw new BusinessException("根據接口惟一碼未獲取到中間表配置信息"); } if (ProString.isBlank(mtDetail.getDriver()) && ProString.isBlank(mtDetail.getUrl()) && ProString.isBlank(mtDetail.getUsername())) { throw new IllegalArgumentException(String.format("接口【%s】未配置中間表信息,沒法切換數據源", interfaceCode)); } Map<String, Object> dataSourceConfigMap = new HashMap<String, Object>(); dataSourceConfigMap.put(DBContextHolder.DATASOURCE_KEY, mtDetail.getDataSourceKey()); dataSourceConfigMap.put(DBContextHolder.DATASOURCE_DRIVER, mtDetail.getDriver()); dataSourceConfigMap.put(DBContextHolder.DATASOURCE_URL, mtDetail.getUrl()); dataSourceConfigMap.put(DBContextHolder.DATASOURCE_USERNAME, mtDetail.getUsername()); dataSourceConfigMap.put(DBContextHolder.DATASOURCE_PASSWORD, mtDetail.getPassword()); LOGGER.info("【指定數據源】dataSourceConfigMap = {}", dataSourceConfigMap); DBContextHolder.setDBType(dataSourceConfigMap); } }
說明:
1.該類的init方法在服務器啓動的時候執行,根據中間表配置信息,初始化數據源。
2.dataSourceSwitch:數據源開關,根據接口惟一碼(一個惟一碼對應一類業務,同一業務和不一樣系統的交互方式可能不一樣,如使用中間庫、webService等)判斷是否使用中間庫,若是是,則根據信息,指定到對應的數據源。
3.這裏引起了一個思考:一個數據源中,爲什麼又要有多個不一樣的數據源?好比文中的AtomikosDynamicDataSource,擁有一個targetDataSources屬性(該屬性在父類AbstractRoutingDataSource中),存儲多個不一樣的數據源。雖然這樣的設計解決了博主的問題,可是目前搞不明白這樣設計的目的。
4.一般意義的數據源是指javax.sql.DataSource類,可是它的子類不只僅只有數據源信息,還包含了鏈接池屬性,如DruidXADataSource。初始化數據源的時候,指定了鏈接池的信息,因此後面切換了數據源以後,沒有每次都建立鏈接等操做,直接從鏈接池取現有的空閒鏈接。
2019-3-26 補充:近日幫不少朋友答疑的時候發現,博文中缺乏一個更爲簡單的示例,缺乏對工做原理的說明,今天補充一下
先看下面一段示例代碼,有2個方法,saveUser2DB1和saveUser2DB2,都是保存用戶信息,可是存入的分別是testUserDB1和testUserDB2。
工做原理:切換數據源的關鍵,就是利用DBContextHolder上下文對象,把數據源的信息放進去,而後在執行SQL以前,會調用AtomikosDynamicDataSource的determineCurrentLookupKey方法,該方法返回的其實就是targetDataSources的key值。在determineCurrentLookupKey方法中,調用了verifyAndInitDataSource方法,判斷對應的數據源是否已經初始化,沒有,則先初始化,再把初始化獲得的DataSource子類(本文中就是AtomikosDynamicDataSource)對象,放入到targetDataSources中,並返回對應的key,數據源切換就完成了,後面執行SQL的時候,就是往對應的數據源中添加了。
@Autowired private UserDao userDao; public void saveUser2DB1(User user){ // 切換數據源。使用上下文對象標記當前須要使用的數據源 Map<String, Object> dataSourceConfigMap = new HashMap<String, Object>(); dataSourceConfigMap.put(DBContextHolder.DATASOURCE_KEY, "testUserDB1"); dataSourceConfigMap.put(DBContextHolder.DATASOURCE_DRIVER, "com.mysql.jdbc.Driver"); dataSourceConfigMap.put(DBContextHolder.DATASOURCE_URL, "jdbc:mysql://localhost:3306/testUserDB1"); dataSourceConfigMap.put(DBContextHolder.DATASOURCE_USERNAME, "root"); dataSourceConfigMap.put(DBContextHolder.DATASOURCE_PASSWORD, "root"); LOGGER.info("【指定數據源】dataSourceConfigMap = {}", dataSourceConfigMap); DBContextHolder.setDBType(dataSourceConfigMap); userDao.save(user); } public void saveUser2DB2(User user){ // 切換數據源。使用上下文對象標記當前須要使用的數據源 Map<String, Object> dataSourceConfigMap = new HashMap<String, Object>(); dataSourceConfigMap.put(DBContextHolder.DATASOURCE_KEY, "testUserDB2"); dataSourceConfigMap.put(DBContextHolder.DATASOURCE_DRIVER, "com.mysql.jdbc.Driver"); dataSourceConfigMap.put(DBContextHolder.DATASOURCE_URL, "jdbc:mysql://localhost:3306/testUserDB2"); dataSourceConfigMap.put(DBContextHolder.DATASOURCE_USERNAME, "root"); dataSourceConfigMap.put(DBContextHolder.DATASOURCE_PASSWORD, "root"); LOGGER.info("【指定數據源】dataSourceConfigMap = {}", dataSourceConfigMap); DBContextHolder.setDBType(dataSourceConfigMap); userDao.save(user); }
以上內容,徹底由博主本身集合網上資料摸索出來的一套業務解決方案,若有缺陷,敬請你們指出交流。
若是有問題,你們能夠留言討論,或加我QQ/發郵件交流,聯繫方式在我我的資料中。因爲平時工做較忙,如未及時回覆你們,敬請見諒。