CONAN ZONE

          你越掙扎我就越興奮

          BlogJava 首頁 新隨筆 聯系 聚合 管理
            0 Posts :: 282 Stories :: 0 Comments :: 0 Trackbacks

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

          問題的分析

          我首先想到在springapplicationContext中配置所有的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模式的結構如圖。

          我們本來需要修改圖中所有具體的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等,這些類我們不可能修改它們本身,更不可能對它們一個個地修改以實現動態分配數據源的功能,同時,我們又希望使用dataSourcesessionFactory根本就感覺不到這樣的變化。Decorator模式就正是解決這個問題的設計模式。

          首先寫一個Decorator類,我取名叫MultiDataSource,通過它來動態切換數據源。同時在配置文件中將sessionFactory的dataSource屬性由原來的某個具體的dataSource改為MultiDataSource。如圖:

          對比原Decorator模式,AbstractComponent是一個抽象類,但在這里我們可以將這個抽象類用接口來代替,即DataSource接口,而ConcreteComponent就是那些DataSource的實現類,如BasicDataSourceJndiObjectFactoryBean等。MultiDataSource封裝了具體的dataSource,并實現了數據源動態切換:
          java 代碼
          1. public class MultiDataSource implements DataSource {   
          2.     private DataSource dataSource = null;   
          3. public MultiDataSource(DataSource dataSource){   
          4.         this.dataSource = dataSource;   
          5.     }   
          6.     /* (non-Javadoc)  
          7.      * @see javax.sql.DataSource#getConnection()  
          8.      */  
          9.     public Connection getConnection() throws SQLException {   
          10.         return getDataSource().getConnection();   
          11.     }   
          12.     //其它DataSource接口應當實現的方法   
          13.   
          14.     public DataSource getDataSource(){   
          15.         return this.dataSource;   
          16.         }   
          17.     }   
          18.     public void setDataSource(DataSource dataSource) {   
          19.         this.dataSource = dataSource;   
          20.     }   
          21. }   

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

          (三)            單例模式下的MultiDataSource

          在單例模式下,由于我們在每次調用MultiDataSource的方法的時候,dataSource都可能是不同的,所以我們不能將dataSource放在實例變量dataSource中,最簡單的方式就是在方法getDataSource()中增加參數,告訴MultiDataSource我到底調用的是哪個dataSource

          java 代碼
          1. public DataSource getDataSource(String dataSourceName){   
          2.         log.debug("dataSourceName:"+dataSourceName);   
          3.         try{   
          4.             if(dataSourceName==null||dataSourceName.equals("")){   
          5.                 return this.dataSource;   
          6.             }   
          7.             return (DataSource)this.applicationContext.getBean(dataSourceName);   
          8.         }catch(NoSuchBeanDefinitionException ex){   
          9.             throw new DaoException("There is not the dataSource 
          10.         }   
          11.     }   

          值得一提的是,我需要的數據源已經都在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>   

          為了得到springApplicationContext,MultiDataSource類必須實現接口org.springframework.context.ApplicationContextAware,并且實現方法:

          java 代碼
          1. private ApplicationContext applicationContext = null;   
          2. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {   
          3.         this.applicationContext = applicationContext;   
          4.     }   

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

          (四)            通過線程傳遞dataSourceName

          查看以上設計,MultiDataSource依然無法運行,因為用戶在發出請求時,他需要連接什么數據庫,其數據源名是放在request中的,要將request中的數據源名傳給MultiDataSource,需要經過BUSDAO,也就是說為了把數據源名傳給MultiDataSource,BUSDAO的所有方法都要增加dataSourceName的參數,這是我們不愿看到的。寫一個類,通過線程的方式跳過BUSDAO直接傳遞給MultiDataSource是一個不錯的設計:

          java 代碼
          1. public class SpObserver {   
          2.     private static ThreadLocal local = new ThreadLocal();   
          3.     public static void putSp(String sp) {   
          4.         local.set(sp);   
          5.     }   
          6.     public static String getSp() {   
          7.         return (String)local.get();   
          8.     }   
          9. }   

          做一個filter,每次客戶發出請求的時候就調用SpObserver.petSp(dataSourceName),將request中的dataSourceName傳遞給SpObserver對象。最后修改MultiDataSource的方法getDataSource()

          java 代碼
          1. public DataSource getDataSource(){   
          2.         String sp = SpObserver.getSp();   
          3.         return getDataSource(sp);   
          4.     }   

          完整的MultiDataSource代碼在附件中。

          (五)            動態添加數據源

          通過以上方案,我們解決了動態分配數據源的問題,但你可能提出疑問:方案中的數據源都是配置在springApplicationContext中,如果我在程序運行過程中動態添加數據源怎么辦?這確實是一個問題,而且在我們的項目中也確實遇到。springApplicationContext是在項目啟動的時候加載的。加載以后,我們如何動態地加載新的beanApplicationContext中呢?我想到如果用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>  

          其中dataSource屬性實際上更準確地說應當是defaultDataSource,即spring啟動時以及在客戶沒有指定數據源時應當指定的默認數據源。

          該方案的優勢

           

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

          首先,這個方案完全是在spring的框架下解決的,數據源依然配置在spring的配置文件中,sessionFactory依然去配置它的dataSource屬性,它甚至都不知道dataSource的改變。唯一不同的是在真正的dataSourcesessionFactory之間增加了一個MultiDataSource。

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

          最后,這個方案可以使單數據源與多數據源兼容。這個方案完全不影響BUSDAO的編寫。如果我們的項目在開始之初是單數據源的情況下開發,隨著項目的進行,需要變更為多數據源,則只需要修改spring配置,并少量修改MVC層以便在請求中寫入需要的數據源名,變更就完成了。如果我們的項目希望改回單數據源,則只需要簡單修改配置文件。這樣,為我們的項目將增加更多的彈性。

          特別說明:實例中的 DynamicLoadBean在web環境下運行會出錯,需要將類中AbstractApplicationContext改為 org.springframework.context.ConfigurableApplicationContext。



          posted on 2009-07-06 13:43 CONAN 閱讀(233) 評論(0)  編輯  收藏 所屬分類: Spring
          主站蜘蛛池模板: 西林县| 南平市| 本溪| 阜新市| 新野县| 宁陕县| 喀喇沁旗| 饶平县| 鄯善县| 和龙市| 万宁市| 沙湾县| 新和县| 彰化市| 哈密市| 工布江达县| 乌兰察布市| 汝南县| 五指山市| 沾化县| 新安县| 扶余县| 汉沽区| 文山县| 贵定县| 武义县| 宁波市| 华坪县| 自贡市| 道真| 老河口市| 共和县| 滁州市| 万全县| 洛宁县| 大竹县| 边坝县| 前郭尔| 乌恰县| 莎车县| 青铜峡市|