天天看點

spring架構中多資料源建立加載并且實作動态切換的配置執行個體代碼

原創不易,轉載請注明出處:

代碼下載下傳位址:

在我們的項目中遇到這樣一個問題:我們的項目需要連接配接多個資料庫,而且不同的客戶在每次通路中根據需要會去通路不同的資料庫。我們以往在spring和hibernate架構中總是配置一個資料源,因而sessionfactory的datasource屬性總是指向這個資料源并且恒定不變,所有dao在使用sessionfactory的時候都是通過這個資料源通路資料庫。但是現在,由于項目的需要,我們的dao在通路sessionfactory的時候都不得不在多個資料源中不斷切換,問題就出現了:如何讓sessionfactory在執行資料持久化的時候,根據客戶的需求能夠動态切換不同的資料源?我們能不能在spring的架構下通過少量修改得到解決?是否有什麼設計模式可以利用呢? 

我首先想到在spring的applicationcontext中配置所有的datasource。這些datasource可能是各種不同類型的,比如不同的資料庫:oracle、sql server、mysql等,也可能是不同的資料源:比如apache 提供的org.apache.commons.dbcp.basicdatasource、spring提供的org.springframework.jndi.jndiobjectfactorybean等。然後sessionfactory根據客戶的每次請求,将datasource屬性設定成不同的資料源,以到達切換資料源的目的。

但是,我很快發現一個問題:當多使用者同時并發通路資料庫的時候會出現資源争用的問題。這都是“單例模式”惹的禍。衆所周知,我們在使用spring架構的時候,在beanfactory中注冊的bean基本上都是采用單例模式,即spring在啟動的時候,這些bean就裝載到記憶體中,并且每個bean在整個項目中隻存在一個對象。正因為隻存在一個對象,對象的所有屬性,更準确說是執行個體變量,表現得就如同是個靜态變量(實際上“靜态”與“單例”往往是非常相似的兩個東西,我們常常用“靜态”來實作“單例”)。拿我們的問題來說,sessionfactory在整個項目中隻有一個對象,它的執行個體變量datasource也就隻有一個,就如同一個靜态變量一般。如果不同的使用者都不斷地去修改datasource的值,必然會出現多使用者争用一個變量的問題,對系統産生隐患。

通過以上的分析,解決多資料源通路問題的關鍵,就集中在sessionfactory在執行資料持久化的時候,能夠通過某段代碼去根據客戶的需要動态切換資料源,并解決資源争用的問題。

采用decorator設計模式

要解決這個問題,我的思路鎖定在了這個datasource上了。如果sessionfactory指向的datasource可以根據客戶的需求去連接配接客戶所需要的真正的資料源,即提供動态切換資料源的功能,那麼問題就解決了。那麼我們怎麼做呢?去修改那些我們要使用的datasource源碼嗎?這顯然不是一個好的方案,我們希望我們的修改與原datasource代碼是分離的。根據以上的分析,使用gof設計模式中的decorator模式(裝飾者模式)應當是我們可以選擇的最佳方案。

什麼是“decorator模式”?簡單點兒說就是當我們需要修改原有的功能,但我們又不願直接去修改原有的代碼時,設計一個decorator套在原有代碼外面。當我們使用decorator的時候與原類完全一樣,當decorator的某些功能卻已經修改為了我們需要修改的功能。decorator模式的結構如圖。

spring架構中多資料源建立加載并且實作動态切換的配置執行個體代碼

我們本來需要修改圖中所有具體的component類的一些功能,但卻并不是去直接修改它們的代碼,而是在它們的外面增加一個decorator。decorator與具體的component類都是繼承的abstractcomponent,是以它長得和具體的component類一樣,也就是說我們在使用decorator的時候就如同在使用concretecomponenta或者concretecomponentb一樣,甚至那些使用concretecomponenta或者concretecomponentb的客戶程式都不知道它們用的類已經改為了decorator,但是decorator已經對具體的component類的部分方法進行了修改,執行這些方法的結果已經不同了。

設計multidatasource類

現在回到我們的問題,我們需要對datasource的功能進行變更,但又不希望修改datasource中的任何代碼。我這裡指的datasource是所有實作javax.sql.datasource接口的類,我們常用的包括apache 提供的org.apache.commons.dbcp.basicdatasource、spring提供的org.springframework.jndi.jndiobjectfactorybean等,這些類我們不可能修改它們本身,更不可能對它們一個個地修改以實作動态配置設定資料源的功能,同時,我們又希望使用datasource的sessionfactory根本就感覺不到這樣的變化。decorator模式就正是解決這個問題的設計模式。

首先寫一個decorator類,我取名叫multidatasource,通過它來動态切換資料源。同時在配置檔案中将sessionfactory的datasource屬性由原來的某個具體的datasource改為multidatasource。如圖:

spring架構中多資料源建立加載并且實作動态切換的配置執行個體代碼

對比原decorator模式,abstractcomponent是一個抽象類,但在這裡我們可以将這個抽象類用接口來代替,即datasource接口,而concretecomponent就是那些datasource的實作類,如basicdatasource、jndiobjectfactorybean等。multidatasource封裝了具體的datasource,并實作了資料源動态切換:

java 代碼

客戶在送出請求的時候,将datasourcename放到request中,然後把request中的資料源名通過調用newmultidatasource(datasource)時可以告訴multidatasource客戶需要的資料源,就可以實作動态切換資料源了。但細心的朋友會發現這在單例的情況下就是問題的,因為multidatasource在系統中隻有一個對象,它的執行個體變量datasource也隻有一個,就如同一個靜态變量一般。正因為如此,單例模式讓許多設計模式都不得不需要更改,這将在我的《“單例”更改了我們的設計模式》中詳細讨論。那麼,我們在單例模式下如何設計呢?

單例模式下的multidatasource

在單例模式下,由于我們在每次調用multidatasource的方法的時候,datasource都可能是不同的,是以我們不能将datasource放在執行個體變量datasource中,最簡單的方式就是在方法getdatasource()中增加參數,告訴multidatasource我到底調用的是哪個datasource:

值得一提的是,我需要的資料源已經都在spring的配置檔案中注冊,datasourcename就是其對應的id。

xml 代碼

為了得到spring的applicationcontext,multidatasource類必須實作接口org.springframework.context.applicationcontextaware,并且實作方法:

如此這樣,我就可以通過this.applicationcontext.getbean(datasourcename)得到datasource了。

通過線程傳遞datasourcename

檢視以上設計,multidatasource依然無法運作,因為使用者在送出請求時,他需要連接配接什麼資料庫,其資料源名是放在request中的,要将request中的資料源名傳給multidatasource,需要經過bus和dao,也就是說為了把資料源名傳給multidatasource,bus和dao的所有方法都要增加datasourcename的參數,這是我們不願看到的。寫一個類,通過線程的方式跳過bus和dao直接傳遞給multidatasource是一個不錯的設計:

spring架構中多資料源建立加載并且實作動态切換的配置執行個體代碼

做一個filter,每次客戶送出請求的時候就調用spobserver.petsp(datasourcename),将request中的datasourcename傳遞給spobserver對象。最後修改multidatasource的方法getdatasource():

完整的multidatasource代碼在附件中。

動态添加資料源

通過以上方案,我們解決了動态配置設定資料源的問題,但你可能提出疑問:方案中的資料源都是配置在spring的applicationcontext中,如果我在程式運作過程中動态添加資料源怎麼辦?這确實是一個問題,而且在我們的項目中也确實遇到。spring的applicationcontext是在項目啟動的時候加載的。加載以後,我們如何動态地加載新的bean到applicationcontext中呢?我想到如果用spring自己的方法解決這個問題就好了。所幸的是,在檢視spring的源代碼後,我找到了這樣的代碼,編寫了dynamicloadbean類,隻要調用loadbean()方法,就可以将某個或某幾個配置檔案中的bean加載到applicationcontext中(見附件)。不通過配置檔案直接加載對象,在spring的源碼中也有,感興趣的朋友可以自己研究。

在spring中配置

在完成了所有這些設計以後,我最後再唠叨一句。我們應當在spring中做如下配置:

其中datasource屬性實際上更準确地說應當是defaultdatasource,即spring啟動時以及在客戶沒有指定資料源時應當指定的預設資料源。

以上方案與其它方案相比,它有哪些優勢呢?

首先,這個方案完全是在spring的架構下解決的,資料源依然配置在spring的配置檔案中,sessionfactory依然去配置它的datasource屬性,它甚至都不知道datasource的改變。唯一不同的是在真正的datasource與sessionfactory之間增加了一個multidatasource。

其次,實作簡單,易于維護。這個方案雖然我說了這麼多東西,其實都是分析,真正需要我們寫的代碼就隻有multidatasource、spobserver兩個類。multidatasource類真正要寫的隻有getdatasource()和getdatasource(sp)兩個方法,而spobserver類更簡單了。實作越簡單,出錯的可能就越小,維護性就越高。

最後,這個方案可以使單資料源與多資料源相容。這個方案完全不影響bus和dao的編寫。如果我們的項目在開始之初是單資料源的情況下開發,随着項目的進行,需要變更為多資料源,則隻需要修改spring配置,并少量修改mvc層以便在請求中寫入需要的資料源名,變更就完成了。如果我們的項目希望改回單資料源,則隻需要簡單修改配置檔案。這樣,為我們的項目将增加更多的彈性。

特别說明:執行個體中的dynamicloadbean在web環境下運作會出錯,需要将類中abstractapplicationcontext改為org.springframework.context.configurableapplicationcontext。

相關部落格: