在前面我寫了《如何在 spring 框架中解決多數(shù)據(jù)源的問題 》,通過設(shè)計模式中的 Decorator 模式在 spring 框架中解決多數(shù)據(jù)源的問題,得到了許多網(wǎng)友的關(guān)注。在與網(wǎng)友探討該問題的過程中,我發(fā)現(xiàn)我的方案并不完善,它只解決了一部分問題。
總結(jié)多數(shù)據(jù)源的問題,其實它需要分為以下三種情況:各個數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)不同、各個數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)相同、各個數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)部分相同又有部分不同。對于第二種情況,各個數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)相同,我們使用一個 sessionFactory ,而在 sessionFactory 中通過 MultiDataSource 來動態(tài)切換數(shù)據(jù)源,應(yīng)當(dāng)是一個不錯的方案,既解決了多個 sessionFactory 對相同的值對象重復(fù)裝載對內(nèi)存的浪費,又使數(shù)據(jù)源的切換對客戶程序透明,簡化了代碼的實現(xiàn)和對客戶程序的影響。但是,對于第一種情況,各個數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)不同,運用這樣的方案存在潛在風(fēng)險。
對于各個數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)不同的情況,使用一個 sessionFactory 而在這個 sessionFactory 中動態(tài)切換數(shù)據(jù)源,可能造成數(shù)據(jù)訪問的張冠李戴。譬如,數(shù)據(jù)源 A 有表 T 而數(shù)據(jù)源 B 沒有,可能造成客戶程序在訪問表 T 的時候卻嘗試去連接數(shù)據(jù)源 B ,因為客戶程序訪問哪個數(shù)據(jù)源是在程序運行期間由客戶程序決定的,因此這樣的錯誤是很難發(fā)現(xiàn)的。也許客戶程序的一個不經(jīng)意的錯誤就可能造成錯誤。解決這個問題的方法有兩個:一是嚴(yán)格要求客戶程序不要寫錯,這當(dāng)然是可以做到的,但作為框架設(shè)計者,另一個解決方法是在框架中就避免出現(xiàn)這樣的情況。因此我祭出了 MultiSessionFactory 的方案來解決各個數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)不同的多數(shù)據(jù)源問題。
問題的分析
與 MultiDataSource 的方案一樣, MultiSessionFactory 同樣是在 spring 框架下調(diào)用 ApplicationContext 的getBean() 方法而不會另外創(chuàng)建 beanFacoty ,也同樣使用 Decorator 模式來處理切換的問題。MultiSessionFactory 的對象關(guān)系如圖:

在該方案中, SessionFactory 就是 Hibernate 的 org.hibernate.SessionFactory 接口, Decorator 就是MultiSessionFactory , SessionFactory1 和 SessionFactory2 往往是 spring 的org.springframework.orm.hibernate3.LocalSessionFactoryBean 。細(xì)心的朋友可能會注意,實際上LocalSessionFactoryBean 并不是 SessionFactory 的實現(xiàn),這個方案是否有問題呢?這個問題其實也一直困擾了我好久,最后我發(fā)現(xiàn),我們通過 ApplicationContext 的 getBean() 得到一個 LocalSessionFactoryBean 的時候其實并不是真正地得到了它,而是得到了一個 SessionFactory ,因為 spring 為 LocalSessionFactoryBean 重寫了getObject() ,使其返回的是 SessionFactory 。一個簡單的明證就是, HibernateDaoSupport 的 sessionFactory屬性的類型是 SessionFactory ,而我們在 spring 配置的時候注入的卻是 LocalSessionFactoryBean 。
方案的實現(xiàn)
在整個這個方案中,我們需要實現(xiàn)的只有 MultiSessionFactory 類和我們可愛的 Spserver ,總共就兩個類,然后呢就是一些 spring 的配置,就完成了。
MultiSessionFactory 實現(xiàn)了 SessionFactory ,同時為了得到 AplicationContext 而實現(xiàn)了ApplicationContextAware 。 MultiSessionFactory 的代碼如下:
java 代碼
- public class MultiSessionFactory implements SessionFactory, ApplicationContextAware {
- private static final long serialVersionUID = 2064557324203496378L;
- private static final Log log = LogFactory.getLog(MultiSessionFactory. class );
- private ApplicationContext applicationContext = null ;
- private SessionFactory sessionFactory = null ;
- public ApplicationContext getApplicationContext() {
- return applicationContext;
- }
- public void setApplicationContext(ApplicationContext applicationContext) {
- this .applicationContext = applicationContext;
- }
- public SessionFactory getSessionFactory(String sessionFactoryName) {
- log.debug( "sessionFactoryName:" +sessionFactoryName);
- try {
- if (sessionFactoryName== null ||sessionFactoryName.equals( "" )){
- return sessionFactory;
- }
- return (SessionFactory) this .getApplicationContext().getBean(sessionFactoryName);
- } catch (NoSuchBeanDefinitionException ex){
- throw new DaoException( "There is not the sessionFactory
- }
- }
-
- public SessionFactory getSessionFactory() {
- String sessionFactoryName = SpObserver.getSp();
- return getSessionFactory(sessionFactoryName);
- }
-
- public void setSessionFactory(SessionFactory sessionFactory) {
- this .sessionFactory = sessionFactory;
- }
-
- // SessionFactory接口需要實現(xiàn)的方法
-
- ......
-
- }
MultiSessionFactory的完整代碼見我提供的附件。 setSessionFactory() 實際上是設(shè)定的默認(rèn)sessionFactory ,它在 spring 裝載的時候調(diào)用,其對應(yīng)的數(shù)據(jù)源應(yīng)當(dāng)是主數(shù)據(jù)源,即項目初始化中需要讀取初始化數(shù)據(jù)的數(shù)據(jù)源。在任何多數(shù)據(jù)源項目中,都應(yīng)當(dāng)有一個存放初始化數(shù)據(jù)、系統(tǒng)維護(hù)數(shù)據(jù)、用戶權(quán)限數(shù)據(jù)的數(shù)據(jù)源,這就是主數(shù)據(jù)源。因此 MultiSessionFactory 的配置應(yīng)當(dāng)這樣寫:
xml 代碼
- < bean id = "sessionFactory" class = "com.htxx.service.dao.MultiSessionFactory" >
- < property name = "sessionFactory" > < ref bean = "hostSessionFactory" />property >
- >
SpServer的寫法與《如何在 spring 框架中解決多數(shù)據(jù)源的問題》 中的一樣,我就不再累贅了。
另外,在 spring 配置中配置多個數(shù)據(jù)源,每個數(shù)據(jù)源對應(yīng)一個 sessionFactory ,這個對應(yīng)的sessionFactory 中的值對象應(yīng)當(dāng)是該數(shù)據(jù)源的值對象。客戶程序在執(zhí)行數(shù)據(jù)訪問前,通過調(diào)用 SpServer 的putSp() 方法,告訴 MultiSessionFactory 需要切換到哪個 sessionFactory ,然后執(zhí)行數(shù)據(jù)訪問。這樣,不同數(shù)據(jù)源的值對象通過放在不同的 sessionFactory 中,避免了張冠李戴的情況。具體的示例見附件的MultiSessionFactoryTest 。
另外的方案
也許有些朋友對以上方案還不滿意,因為在執(zhí)行數(shù)據(jù)訪問前畢竟還要多做一步指定 sessionFactory 的工作。實際上,對于各個數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)不同的項目,一個值對象應(yīng)當(dāng)使用哪個數(shù)據(jù)源有一個非常確定的對應(yīng)關(guān)系。如果通過配置文件將值對象與它的 sessionFactory 對應(yīng)起來,那么我們在執(zhí)行數(shù)據(jù)訪問的時候傳遞的是哪個值對象, MultiSessionFactory 馬上就可以去找到對應(yīng)的 sessionFactory 。這個方案你可以通過 AOP 來制作一個攔截器攔截所有諸如 save() 、 delete() 、 get() 、 load() 等方法來實現(xiàn),也可以擴(kuò)展 HibernateDaoSupport 來實現(xiàn)。這樣的方案使客戶程序甚至都不用知道他是在操作的一個多數(shù)據(jù)源系統(tǒng)。當(dāng)然,這個方案感興趣的朋友可以自己去實現(xiàn)。
另外,在這個方案中的核心是運用 Decorator 設(shè)計模式來解決切換 sessionFactory 的目的,即MultiSessionFactory 的實現(xiàn)。至于通過什么方式來通知 MultiSessionFactory 應(yīng)當(dāng)切換到哪個 SessionFactory ,可以根據(jù)不同項目的情況自由選擇。我在這里給大家提供了通過 SpOberver 和建立值對象與 sessionFactory 關(guān)系的配置文件這兩個方案,你也可以有自己的方案解決。
第三種情況的解決方案
前面我已經(jīng)給出了第一種和第二種情況的解決方案:各個數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)不同的情況用MultiSessionFactory 解決;各個數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)相同的情況用 MultiDataSource 解決。那么第三種情況,各個數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)部分相同又有部分不同,又應(yīng)當(dāng)如何解決呢?當(dāng)然是將 MultiSessionFactory 和MultiDataSource 結(jié)合起來解決。對于數(shù)據(jù)結(jié)構(gòu)不同的部分,其分別創(chuàng)建各自的 sessionFactory 然后通過MultiSessionFactory 來切換,而對于數(shù)據(jù)結(jié)構(gòu)相同的部分,建立共同的 sessionFactory 和多個不同的dataSource 然后通過 MultiDataSource 來切換就可以了。
還有的朋友問到這樣的方案其事務(wù)處理和二級緩存的情況。這個方案是在 spring 框架下的解決方案,其事務(wù)處理的能力也是由 spring 的能力來決定的。目前 spring 要處理跨數(shù)據(jù)庫的事務(wù)處理是通過 JTA 來實現(xiàn)的,這種方式在該方案中同樣可以實現(xiàn),朋友們可以試一試。另外,本方案能使用二級緩存嗎?當(dāng)然可以。對于MultiSessionFactory 當(dāng)然沒有任何問題,它通過不同的 sessionFactory 分離開了不同的數(shù)據(jù)源和值對象,我們可以毫無顧忌地使用。對于 MultiDataSource 來說,就有點問題了。 MultiDataSource 使多個數(shù)據(jù)源使用共同的sessionFactory ,因此它仿佛就是將多個數(shù)據(jù)源在邏輯上合并為一個數(shù)據(jù)源。正因為如此,我們需要保證對于同一個表在所有數(shù)據(jù)源中都要主鍵唯一。什么意思呢?數(shù)據(jù)源 A 和數(shù)據(jù)源 B 都有表 T ,如果數(shù)據(jù)源 A 中的表 T 擁有 ID 為 001 的一條數(shù)據(jù),那么在數(shù)據(jù)源 B 的表 T 中就不能有 ID 為 001 的記錄。如果你總是通過MultiDataSource 來執(zhí)行表的插入操作,并且使用uuid.hex生成主鍵,這當(dāng)然不會有問題。但如果你有通過其它方式插入表的操作,你應(yīng)當(dāng)保證這樣的唯一性。另外,對于查詢的操作,緩存中存放的既可能是數(shù)據(jù)源 A 的數(shù)據(jù),也可能是數(shù)據(jù)源 B 的數(shù)據(jù),因此你應(yīng)當(dāng)對數(shù)據(jù)有一個規(guī)劃。對于表 T 的數(shù)據(jù),哪些應(yīng)當(dāng)插入到數(shù)據(jù)源 A 中,哪些應(yīng)當(dāng)插入到 B 中,應(yīng)當(dāng)有一個定義。假如是通過不同單位來決定插入哪個數(shù)據(jù)源,那么在查詢數(shù)據(jù)源 A 的表 T是,應(yīng)當(dāng)增加條件只查詢數(shù)據(jù)源 A 應(yīng)當(dāng)有的單位而排除調(diào)其它單位。如此這樣,你只要注意到這兩個問題,你就可以放心大膽地使用二級緩存。
作者:mixer_a 發(fā)表于2012-4-11 21:24:29
原文鏈接