在前面我寫了《如何在 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 代碼
  1. public   class  MultiSessionFactory  implements  SessionFactory, ApplicationContextAware {   
  2.      private   static   final   long  serialVersionUID = 2064557324203496378L;   
  3.      private   static   final  Log log = LogFactory.getLog(MultiSessionFactory. class );   
  4.      private  ApplicationContext applicationContext =  null ;   
  5.      private  SessionFactory sessionFactory =  null ;   
  6.      public  ApplicationContext getApplicationContext() {   
  7.          return  applicationContext;   
  8.     }   
  9.      public   void  setApplicationContext(ApplicationContext applicationContext) {   
  10.         this .applicationContext = applicationContext;   
  11.     }   
  12.      public  SessionFactory getSessionFactory(String sessionFactoryName) {   
  13.        log.debug( "sessionFactoryName:" +sessionFactoryName);   
  14.         try {   
  15.             if (sessionFactoryName== null ||sessionFactoryName.equals( "" )){   
  16.                return  sessionFactory;   
  17.            }   
  18.             return  (SessionFactory) this .getApplicationContext().getBean(sessionFactoryName);   
  19.        } catch (NoSuchBeanDefinitionException ex){   
  20.             throw   new  DaoException( "There is not the sessionFactory 
  21.        }   
  22.     }   
  23.   
  24.      public  SessionFactory getSessionFactory() {   
  25.        String sessionFactoryName = SpObserver.getSp();   
  26.         return  getSessionFactory(sessionFactoryName);   
  27.     }   
  28.   
  29.      public   void  setSessionFactory(SessionFactory sessionFactory) {   
  30.         this .sessionFactory = sessionFactory;   
  31.     }   
  32.   
  33.      // SessionFactory接口需要實現(xiàn)的方法    
  34.   
  35. ......   
  36.   
  37. }  

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 代碼
  1. < bean   id = "sessionFactory"   class = "com.htxx.service.dao.MultiSessionFactory" >   
  2.      < property   name = "sessionFactory" > < ref   bean = "hostSessionFactory" />property >  
  3. >   

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 原文鏈接
閱讀:4 評論:0 查看評論