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

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