在我們的專案中遇到這樣一個問題:我們的專案需要連接多個資料庫, 而且不同的客戶在每次訪問中根據需要會去存取不同的資料庫。我們以往在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模式的結構如圖。
我們本來需要修改圖中所有特定的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。如圖:
對比原Decorator模式,AbstractComponent是一個抽象類,但在這裡我們可以將這個抽象類別用介面來代替,即DataSource介面,而ConcreteComponent就是那些DataSource的實作類,如BdiasicDataSource、 Jndi等。 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 程式碼
<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 程式碼
rree💎 dataSourceName)得到dataSource了。 透過線程傳遞dataSourceName查看以上設計,MultiDataSource仍然無法運行,因為用戶在發出請求時,他需要連接什麼資料庫,其資料來源名稱是放在request中的, 要將request中的資料來源名傳給MultiDataSource,需要經過BUS和DAO,也就是說為了把資料來源名稱傳給MultiDataSource,BUS和DAO的所有方法都要增加dataSourceName的參數,這是我們不願意看到的。寫一個類,透過線程的方式跳過BUS和DAO直接傳遞給MultiDataSource是一個不錯的設計:java 程式碼private ApplicationContext applicationContext = null; public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }
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(); } }
通过以上方案,我们解决了动态分配数据源的问题,但你可能提出疑问:方案中的数据源都是配置在spring的ApplicationContext 中,如果我在程序运行过程中动态添加数据源怎么办?这确实是一个问题,而且在我们的项目中也确实遇到。spring的 ApplicationContext是在项目启动的时候加载的。加载以后,我们如何动态地加载新的bean到ApplicationContext中 呢?我想到如果用spring自己的方法解决这个问题就好了。所幸的是,在查看spring的源代码后,我找到了这样的代码,编写了 DynamicLoadBean类,只要调用loadBean()方法,就可以将某个或某几个配置文件中的bean加载到 ApplicationContext中(见附件)。不通过配置文件直接加载对象,在spring的源码中也有,感兴趣的朋友可以自己研究。
在spring中配置
在完成了所有这些设计以后,我最后再唠叨一句。我们应当在spring中做如下配置:
xml 代码
<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。
更多spring框架中多資料來源建立載入並且實現動態切換的配置實例代碼相关文章请关注PHP中文网!