Spring實(shí)現(xiàn)動(dòng)態(tài)切換多數(shù)據(jù)源的解決方案
前言
Spring動(dòng)態(tài)配置多數(shù)據(jù)源,即在大型應(yīng)用中對(duì)數(shù)據(jù)進(jìn)行切分,并且采用多個(gè)數(shù)據(jù)庫(kù)實(shí)例進(jìn)行管理,這樣可以有效提高系統(tǒng)的水平伸縮性。而這樣的方案就會(huì)不同于常見的單一數(shù)據(jù)實(shí)例的方案,這就要程序在運(yùn)行時(shí)根據(jù)當(dāng)時(shí)的請(qǐng)求及系統(tǒng)狀態(tài)來動(dòng)態(tài)的決定將數(shù)據(jù)存儲(chǔ)在哪個(gè)數(shù)據(jù)庫(kù)實(shí)例中,以及從哪個(gè)數(shù)據(jù)庫(kù)提取數(shù)據(jù)。
Spring2.x以后的版本中采用Proxy模式,就是我們?cè)诜桨钢袑?shí)現(xiàn)一個(gè)虛擬的數(shù)據(jù)源,并且用它來封裝數(shù)據(jù)源選擇邏輯,這樣就可以有效地將數(shù)據(jù)源選擇邏輯從Client中分離出來。Client提供選擇所需的上下文(因?yàn)檫@是Client所知道的),由虛擬的DataSource根據(jù)Client提供的上下文來實(shí)現(xiàn)數(shù)據(jù)源的選擇。
實(shí)現(xiàn)
具體的實(shí)現(xiàn)就是,虛擬的DataSource僅需繼承AbstractRoutingDataSource實(shí)現(xiàn)determineCurrentLookupKey()
在其中封裝數(shù)據(jù)源的選擇邏輯。
一、動(dòng)態(tài)配置多數(shù)據(jù)源
1. 數(shù)據(jù)源的名稱常量類:
/** * 動(dòng)態(tài)配置多數(shù)據(jù)源 * 數(shù)據(jù)源的名稱常量類 * @author LONGHUI_LUO * */ public class DataSourceConst { public static final String TEST="test"; public static final String USER="User"; }
2. 建立一個(gè)獲得和設(shè)置上下文環(huán)境的類,主要負(fù)責(zé)改變上下文數(shù)據(jù)源的名稱:
/** * 獲得和設(shè)置上下文環(huán)境 主要負(fù)責(zé)改變上下文數(shù)據(jù)源的名稱 * * @author LONGHUI_LUO * */ public class DataSourceContextHolder { private static final ThreadLocal contextHolder = new ThreadLocal(); // 線程本地環(huán)境 // 設(shè)置數(shù)據(jù)源類型 public static void setDataSourceType(String dataSourceType) { contextHolder.set(dataSourceType); } // 獲取數(shù)據(jù)源類型 public static String getDataSourceType() { return (String) contextHolder.get(); } // 清除數(shù)據(jù)源類型 public static void clearDataSourceType() { contextHolder.remove(); } }
3. 建立動(dòng)態(tài)數(shù)據(jù)源類,注意,這個(gè)類必須繼承AbstractRoutingDataSource,且實(shí)現(xiàn)方法determineCurrentLookupKey,該方法返回一個(gè)Object,一般是返回字符串:
/** * 建立動(dòng)態(tài)數(shù)據(jù)源 * * @author LONGHUI_LUO * */ public class DynamicDataSource extends AbstractRoutingDataSource { protected Object determineCurrentLookupKey() { // 在進(jìn)行DAO操作前,通過上下文環(huán)境變量,獲得數(shù)據(jù)源的類型 return DataSourceContextHolder.getDataSourceType(); } }
4. 編寫spring的配置文件配置多個(gè)數(shù)據(jù)源
<!-- 數(shù)據(jù)源相同的內(nèi)容 --> <bean id="parentDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" /> <property name="username" value="sa" /> <property name="password" value="net2com" /> </bean> <!-- start以下配置各個(gè)數(shù)據(jù)源的特性 --> <bean parent="parentDataSource" id="testDataSource"> <propertynamepropertyname="url" value="jdbc:sqlserver://localhost:1433;databaseName=test" /> </bean> <bean parent="parentDataSource" id="UserDataSource"> <property name="url" value="jdbc:sqlserver://localhost:1433;databaseName=User" /> </bean> <!-- end 配置各個(gè)數(shù)據(jù)源的特性 -->
5. 編寫spring配置文件配置多數(shù)據(jù)源映射關(guān)系
<bean class="com.xxxx.datasouce.DynamicDataSource" id="dataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <entry value-ref="testDataSource" key="test"></entry> <entry value-ref="UserDataSource" key="User"></entry> </map> </property> <property name="defaultTargetDataSource" ref="testDataSource" ></property> </bean>
在這個(gè)配置中第一個(gè)property屬性配置目標(biāo)數(shù)據(jù)源,<map key-type="java.lang.String">
中的key-type必須要和靜態(tài)鍵值對(duì)照類DataSourceConst中的值的類型相 同;<entry key="User" value-ref="userDataSource"/>
中key的值必須要和靜態(tài)鍵值對(duì)照類中的值相同,如果有多個(gè)值,可以配置多個(gè)< entry>標(biāo)簽。第二個(gè)property屬性配置默認(rèn)的數(shù)據(jù)源。
動(dòng)態(tài)切換是數(shù)據(jù)源
DataSourceContextHolder.setDataSourceType(DataSourceConst.TEST);
該方案的優(yōu)勢(shì)
首先,這個(gè)方案完全是在spring的框架下解決的,數(shù)據(jù)源依然配置在spring的配置文件中,sessionFactory依然去配置它的dataSource屬性,它甚至都不知道dataSource的改變。唯一不同的是在真正的dataSource與sessionFactory之間增加了一個(gè)MultiDataSource。
其次,實(shí)現(xiàn)簡(jiǎn)單,易于維護(hù)。這個(gè)方案雖然我說了這么多東西,其實(shí)都是分析,真正需要我們寫的代碼就只有MultiDataSource、SpObserver兩個(gè)類。MultiDataSource類真正要寫的只有getDataSource()
和getDataSource(sp)
兩個(gè)方法,而SpObserver類更簡(jiǎn)單了。實(shí)現(xiàn)越簡(jiǎn)單,出錯(cuò)的可能就越小,維護(hù)性就越高。
最后,這個(gè)方案可以使單數(shù)據(jù)源與多數(shù)據(jù)源兼容。這個(gè)方案完全不影響B(tài)US和DAO的編寫。如果我們的項(xiàng)目在開始之初是單數(shù)據(jù)源的情況下開發(fā),隨著項(xiàng)目的進(jìn)行,需要變更為多數(shù)據(jù)源,則只需要修改spring配置,并少量修改MVC層以便在請(qǐng)求中寫入需要的數(shù)據(jù)源名,變更就完成了。如果我們的項(xiàng)目希望改回單數(shù)據(jù)源,則只需要簡(jiǎn)單修改配置文件。這樣,為我們的項(xiàng)目將增加更多的彈性。
該方案的缺點(diǎn)
沒有能夠解決多用戶訪問單例“sessionFactory”時(shí)共享“dataSource”變量,導(dǎo)致產(chǎn)生爭(zhēng)搶“dataSource”的結(jié)果,本質(zhì)類似于操作系統(tǒng)中的“生產(chǎn)者消費(fèi)者”問題。因此當(dāng)多用戶訪問時(shí),多數(shù)據(jù)源可能會(huì)導(dǎo)致系統(tǒng)性能下降的后果。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流。
相關(guān)文章
Java Map 在put值時(shí)value值不被覆蓋的解決辦法
這篇文章主要介紹了Java Map 在put值時(shí)value值不被覆蓋的解決辦法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-04-04java實(shí)現(xiàn)socket客戶端連接服務(wù)端
本文是個(gè)人剛剛開始學(xué)習(xí)如何通過socket去發(fā)送信息下邊的案例,也是書上的在這留下筆記,最后附上一個(gè)實(shí)例,有需要的小伙伴可以參考下。2015-10-10Spring Boot配置讀取實(shí)現(xiàn)方法解析
這篇文章主要介紹了Spring Boot配置讀取實(shí)現(xiàn)方法解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08詳解Spring Boot讀取配置文件與配置文件優(yōu)先級(jí)
這篇文章主要介紹了詳解Spring Boot讀取配置文件與配置文件優(yōu)先級(jí),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08SpringBoot實(shí)現(xiàn)本地上傳文件到resources目錄
Java后端項(xiàng)目上傳文件是一個(gè)很常見的需求,這篇文章主要為大家介紹了SpringBoot如何實(shí)現(xiàn)本地上傳文件到resources目錄永久保存下載,需要的可以參考一下2023-07-07Spring Boot security 默認(rèn)攔截靜態(tài)資源的解決方法
這篇文章主要介紹了Spring Boot security 默認(rèn)攔截靜態(tài)資源,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03CountDownLatch源碼解析之countDown()
這篇文章主要為大家詳細(xì)解析了CountDownLatch源碼之countDown方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04java實(shí)現(xiàn)大數(shù)加法(BigDecimal)的實(shí)例代碼
之前寫過用vector、string實(shí)現(xiàn)大數(shù)加法,現(xiàn)在用java的BigDecimal類,代碼簡(jiǎn)單很多。但是在online-judge上,java的代碼運(yùn)行時(shí)間和內(nèi)存大得多2013-10-10