在前面我寫了《如何在 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;
- }
-
- // 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 中的值對象應當是該數據源的值對象??蛻舫绦蛟趫绦袛祿L問前,通過調用 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 應當有的單位而排除調其它單位。如此這樣,你只要注意到這兩個問題,你就可以放心大膽地使用二級緩存。
作者:mixer_a 發表于2012-4-11 21:24:29
原文鏈接