spring框架中多資料來源建立載入並且實現動態切換的配置例項程式碼

hddyhg發表於2018-11-09

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

原創不易,轉載請註明出處:spring框架中多資料來源建立載入並且實現動態切換的配置例項程式碼

程式碼下載地址:http://www.zuidaima.com/share/1774074130205696.htm

在我們的專案中遇到這樣一個問題:我們的專案需要連線多個資料庫,而且不同的客戶在每次訪問中根據需要會去訪問不同的資料庫。我們以往在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模式的結構如圖。

Decorator模式的結構

 

我們本來需要修改圖中所有具體的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。如圖:

 

MultiDataSource

對比原Decorator模式,AbstractComponent是一個抽象類,但在這裡我們可以將這個抽象類用介面來代替,即DataSource介面,而ConcreteComponent就是那些DataSource的實現類,如BasicDataSource、JndiObjectFactoryBean等。MultiDataSource封裝了具體的dataSource,並實現了資料來源動態切換:

java 程式碼

public class MultiDataSource implements DataSource {       private DataSource dataSource = null;   public MultiDataSource(DataSource dataSource){           this.dataSource = dataSource;       }       /* (non-Javadoc)       * @see javax.sql.DataSource#getConnection()       */      public Connection getConnection() throws SQLException {           return getDataSource().getConnection();       }       //其它DataSource介面應當實現的方法         public DataSource getDataSource(){           return this.dataSource;           }       }       public void setDataSource(DataSource dataSource) {           this.dataSource = dataSource;       }   }   

 

客戶在發出請求的時候,將dataSourceName放到request中,然後把request中的資料來源名通過呼叫newMultiDataSource(dataSource)時可以告訴MultiDataSource客戶需要的資料來源,就可以實現動態切換資料來源了。但細心的朋友會發現這在單例的情況下就是問題的,因為MultiDataSource在系統中只有一個物件,它的例項變數dataSource也只有一個,就如同一個靜態變數一般。正因為如此,單例模式讓許多設計模式都不得不需要更改,這將在我的《“單例”更改了我們的設計模式》中詳細討論。那麼,我們在單例模式下如何設計呢?

 

  • 單例模式下的MultiDataSource

在單例模式下,由於我們在每次呼叫MultiDataSource的方法的時候,dataSource都可能是不同的,所以我們不能將dataSource放在例項變數dataSource中,最簡單的方式就是在方法getDataSource()中增加引數,告訴MultiDataSource我到底呼叫的是哪個dataSource:

 

java 程式碼

public DataSource getDataSource(String dataSourceName){           log.debug("dataSourceName:"+dataSourceName);           try{               if(dataSourceName==null||dataSourceName.equals("")){                   return this.dataSource;               }               return (DataSource)this.applicationContext.getBean(dataSourceName);           }catch(NoSuchBeanDefinitionException ex){               throw new DaoException("There is not the dataSource         }       }   

 

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

 

xml 程式碼

  1. <bean id="dataSource1"    
  2.     class="org.apache.commons.dbcp.BasicDataSource">    
  3.     <property name="driverClassName">    
  4.         <value>oracle.jdbc.driver.OracleDrivervalue>    
  5.     property>   
  6.     ......     
  7. bean>    
  8. <bean id="dataSource2"    
  9.     class="org.apache.commons.dbcp.BasicDataSource">    
  10.     <property name="driverClassName">    
  11.         <value>oracle.jdbc.driver.OracleDrivervalue>   
  12.     property>     
  13.     ......     
  14. bean>     
<bean id="dataSource1"      class="org.apache.commons.dbcp.BasicDataSource">      <property name="driverClassName">          <value>oracle.jdbc.driver.OracleDrivervalue>      property>     ......   bean>  <bean id="dataSource2"      class="org.apache.commons.dbcp.BasicDataSource">      <property name="driverClassName">          <value>oracle.jdbc.driver.OracleDrivervalue>     property>       ......   bean>   

為了得到spring的ApplicationContext,MultiDataSource類必須實現介面org.springframework.context.ApplicationContextAware,並且實現方法:

java 程式碼

private ApplicationContext applicationContext = null;   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {           this.applicationContext = applicationContext;       }  

 

 

如此這樣,我就可以通過this.applicationContext.getBean(dataSourceName)得到dataSource了。

 

  • 通過執行緒傳遞dataSourceName

檢視以上設計,MultiDataSource依然無法執行,因為使用者在發出請求時,他需要連線什麼資料庫,其資料來源名是放在request中的,要將request中的資料來源名傳給MultiDataSource,需要經過BUS和DAO,也就是說為了把資料來源名傳給MultiDataSource,BUS和DAO的所有方法都要增加dataSourceName的引數,這是我們不願看到的。寫一個類,通過執行緒的方式跳過BUS和DAO直接傳遞給MultiDataSource是一個不錯的設計:

 

MultiDataSource

java 程式碼

public class SpObserver {       private static ThreadLocal local = new ThreadLocal();       public static void putSp(String sp) {           local.set(sp);       }       public static String getSp() {           return (String)local.get();       }   }   

 

做一個filter,每次客戶發出請求的時候就呼叫SpObserver.petSp(dataSourceName),將request中的dataSourceName傳遞給SpObserver物件。最後修改MultiDataSource的方法getDataSource():

 

java 程式碼

public DataSource getDataSource(){           String sp = SpObserver.getSp();           return getDataSource(sp);       }   

 

完整的MultiDataSource程式碼在附件中。

 

  • 動態新增資料來源

通過以上方案,我們解決了動態分配資料來源的問題,但你可能提出疑問:方案中的資料來源都是配置在spring的ApplicationContext中,如果我在程式執行過程中動態新增資料來源怎麼辦?這確實是一個問題,而且在我們的專案中也確實遇到。spring的ApplicationContext是在專案啟動的時候載入的。載入以後,我們如何動態地載入新的bean到ApplicationContext中呢?我想到如果用spring自己的方法解決這個問題就好了。所幸的是,在檢視spring的原始碼後,我找到了這樣的程式碼,編寫了DynamicLoadBean類,只要呼叫loadBean()方法,就可以將某個或某幾個配置檔案中的bean載入到ApplicationContext中(見附件)。不通過配置檔案直接載入物件,在spring的原始碼中也有,感興趣的朋友可以自己研究。

 

 

  • 在spring中配置

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

 

xml 程式碼

  1. <bean id="dynamicLoadBean" class="com.htxx.service.dao.DynamicLoadBean">bean>    
  2. <bean id="dataSource" class="com.htxx.service.dao.MultiDataSource">    
  3.         <property name="dataSource">    
  4.             <ref bean="dataSource1" />    
  5.         property>    
  6.     bean>    
  7.     <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">    
  8.         <property name="dataSource">    
  9.             <ref bean="dataSource" />    
  10.         property>    
  11.         ......     
  12.     bean>    
<bean id="dynamicLoadBean" class="com.htxx.service.dao.DynamicLoadBean">bean>  <bean id="dataSource" class="com.htxx.service.dao.MultiDataSource">          <property name="dataSource">              <ref bean="dataSource1" />          property>      bean>      <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">          <property name="dataSource">              <ref bean="dataSource" />          property>          ......       bean>  

 

其中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。

 

程式碼下載地址:http://dl.iteye.com/topics/download/11b45eb4-9732-4fcb-85de-dd88238958b2

相關部落格:再析在spring框架中解決多資料來源的問題

           

給我老師的人工智慧教程打call!http://blog.csdn.net/jiangjunshow

這裡寫圖片描述

相關文章