在前面我寫了《 如何在 spring 框架中解決多數(shù)據(jù)源的問題 》,通過設(shè)計(jì)模式中的 Decorator 模式在 spring 框架中解決多數(shù)據(jù)源的問題,得到了許多網(wǎng)友的關(guān)注。在與網(wǎng)友探討該問題的過程中,我發(fā)現(xiàn)我的方案并不完善,它只解決了一部分問題。
總結(jié)多數(shù)據(jù)源的問題,其實(shí)它需要分為以下三種情況:各個(gè)數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)不同、各個(gè)數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)相同、各個(gè)數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)部分相同又有部分不同。對(duì)于第二種情況,各個(gè)數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)相同,我們使用一個(gè) sessionFactory ,而在 sessionFactory 中通過 MultiDataSource 來動(dòng)態(tài)切換數(shù)據(jù)源,應(yīng)當(dāng)是一個(gè)不錯(cuò)的方案,既解決了多個(gè) sessionFactory 對(duì)相同的值對(duì)象重復(fù)裝載對(duì)內(nèi)存的浪費(fèi),又使數(shù)據(jù)源的切換對(duì)客戶程序透明,簡化了代碼的實(shí)現(xiàn)和對(duì)客戶程序的影響。但是,對(duì)于第一種情況,各個(gè)數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)不同,運(yùn)用這樣的方案存在潛在風(fēng)險(xiǎn)。
對(duì)于各個(gè)數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)不同的情況,使用一個(gè) sessionFactory 而在這個(gè) sessionFactory 中動(dòng)態(tài)切換數(shù)據(jù)源,可能造成數(shù)據(jù)訪問的張冠李戴。譬如,數(shù)據(jù)源 A 有表 T 而數(shù)據(jù)源 B 沒有,可能造成客戶程序在訪問表 T 的時(shí)候卻嘗試去連接數(shù)據(jù)源 B ,因?yàn)榭蛻舫绦蛟L問哪個(gè)數(shù)據(jù)源是在程序運(yùn)行期間由客戶程序決定的,因此這樣的錯(cuò)誤是很難發(fā)現(xiàn)的。也許客戶程序的一個(gè)不經(jīng)意的錯(cuò)誤就可能造成錯(cuò)誤。解決這個(gè)問題的方法有兩個(gè):一是嚴(yán)格要求客戶程序不要寫錯(cuò),這當(dāng)然是可以做到的,但作為框架設(shè)計(jì)者,另一個(gè)解決方法是在框架中就避免出現(xiàn)這樣的情況。因此我祭出了 MultiSessionFactory 的方案來解決各個(gè)數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)不同的多數(shù)據(jù)源問題。
問題的分析
與 MultiDataSource 的方案一樣, MultiSessionFactory 同樣是在 spring 框架下調(diào)用 ApplicationContext 的 getBean() 方法而不會(huì)另外創(chuàng)建 beanFacoty ,也同樣使用 Decorator 模式來處理切換的問題。 MultiSessionFactory 的對(duì)象關(guān)系如圖:
?
在該方案中, SessionFactory 就是 Hibernate 的 org.hibernate.SessionFactory 接口, Decorator 就是 MultiSessionFactory , SessionFactory1 和 SessionFactory2 往往是 spring 的 org.springframework.orm.hibernate3.LocalSessionFactoryBean 。細(xì)心的朋友可能會(huì)注意,實(shí)際上 LocalSessionFactoryBean 并不是 SessionFactory 的實(shí)現(xiàn),這個(gè)方案是否有問題呢?這個(gè)問題其實(shí)也一直困擾了我好久,最后我發(fā)現(xiàn),我們通過 ApplicationContext 的 getBean() 得到一個(gè) LocalSessionFactoryBean 的時(shí)候其實(shí)并不是真正地得到了它,而是得到了一個(gè) SessionFactory ,因?yàn)? spring 為 LocalSessionFactoryBean 重寫了 getObject() ,使其返回的是 SessionFactory 。一個(gè)簡單的明證就是, HibernateDaoSupport 的 sessionFactory 屬性的類型是 SessionFactory ,而我們?cè)? spring 配置的時(shí)候注入的卻是 LocalSessionFactoryBean 。
方案的實(shí)現(xiàn)
在整個(gè)這個(gè)方案中,我們需要實(shí)現(xiàn)的只有 MultiSessionFactory 類和我們可愛的 Spserver ,總共就兩個(gè)類,然后呢就是一些 spring 的配置,就完成了。
MultiSessionFactory 實(shí)現(xiàn)了 SessionFactory ,同時(shí)為了得到 AplicationContext 而實(shí)現(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接口需要實(shí)現(xiàn)的方法 ??
- ??
- ...... ??
- ??
- }??
MultiSessionFactory 的完整代碼見我提供的附件。 setSessionFactory() 實(shí)際上是設(shè)定的默認(rèn) sessionFactory ,它在 spring 裝載的時(shí)候調(diào)用,其對(duì)應(yīng)的數(shù)據(jù)源應(yīng)當(dāng)是主數(shù)據(jù)源,即項(xiàng)目初始化中需要讀取初始化數(shù)據(jù)的數(shù)據(jù)源。在任何多數(shù)據(jù)源項(xiàng)目中,都應(yīng)當(dāng)有一個(gè)存放初始化數(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 配置中配置多個(gè)數(shù)據(jù)源,每個(gè)數(shù)據(jù)源對(duì)應(yīng)一個(gè) sessionFactory ,這個(gè)對(duì)應(yīng)的 sessionFactory 中的值對(duì)象應(yīng)當(dāng)是該數(shù)據(jù)源的值對(duì)象。客戶程序在執(zhí)行數(shù)據(jù)訪問前,通過調(diào)用 SpServer 的 putSp() 方法,告訴 MultiSessionFactory 需要切換到哪個(gè) sessionFactory ,然后執(zhí)行數(shù)據(jù)訪問。這樣,不同數(shù)據(jù)源的值對(duì)象通過放在不同的 sessionFactory 中,避免了張冠李戴的情況。具體的示例見附件的 MultiSessionFactoryTest 。
另外的方案
也許有些朋友對(duì)以上方案還不滿意,因?yàn)樵趫?zhí)行數(shù)據(jù)訪問前畢竟還要多做一步指定 sessionFactory 的工作。實(shí)際上,對(duì)于各個(gè)數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)不同的項(xiàng)目,一個(gè)值對(duì)象應(yīng)當(dāng)使用哪個(gè)數(shù)據(jù)源有一個(gè)非常確定的對(duì)應(yīng)關(guān)系。如果通過配置文件將值對(duì)象與它的 sessionFactory 對(duì)應(yīng)起來,那么我們?cè)趫?zhí)行數(shù)據(jù)訪問的時(shí)候傳遞的是哪個(gè)值對(duì)象, MultiSessionFactory 馬上就可以去找到對(duì)應(yīng)的 sessionFactory 。這個(gè)方案你可以通過 AOP 來制作一個(gè)攔截器攔截所有諸如 save() 、 delete() 、 get() 、 load() 等方法來實(shí)現(xiàn),也可以擴(kuò)展 HibernateDaoSupport 來實(shí)現(xiàn)。這樣的方案使客戶程序甚至都不用知道他是在操作的一個(gè)多數(shù)據(jù)源系統(tǒng)。當(dāng)然,這個(gè)方案感興趣的朋友可以自己去實(shí)現(xiàn)。
另外,在這個(gè)方案中的核心是運(yùn)用 Decorator 設(shè)計(jì)模式來解決切換 sessionFactory 的目的,即 MultiSessionFactory 的實(shí)現(xiàn)。至于通過什么方式來通知 MultiSessionFactory 應(yīng)當(dāng)切換到哪個(gè) SessionFactory ,可以根據(jù)不同項(xiàng)目的情況自由選擇。我在這里給大家提供了通過 SpOberver 和建立值對(duì)象與 sessionFactory 關(guān)系的配置文件這兩個(gè)方案,你也可以有自己的方案解決。
第三種情況的解決方案
前面我已經(jīng)給出了第一種和第二種情況的解決方案:各個(gè)數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)不同的情況用 MultiSessionFactory 解決;各個(gè)數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)相同的情況用 MultiDataSource 解決。那么第三種情況,各個(gè)數(shù)據(jù)源的數(shù)據(jù)結(jié)構(gòu)部分相同又有部分不同,又應(yīng)當(dāng)如何解決呢?當(dāng)然是將 MultiSessionFactory 和 MultiDataSource 結(jié)合起來解決。對(duì)于數(shù)據(jù)結(jié)構(gòu)不同的部分,其分別創(chuàng)建各自的 sessionFactory 然后通過 MultiSessionFactory 來切換,而對(duì)于數(shù)據(jù)結(jié)構(gòu)相同的部分,建立共同的 sessionFactory 和多個(gè)不同的 dataSource 然后通過 MultiDataSource 來切換就可以了。
還有的朋友問到這樣的方案其事務(wù)處理和二級(jí)緩存的情況。這個(gè)方案是在 spring 框架下的解決方案,其事務(wù)處理的能力也是由 spring 的能力來決定的。目前 spring 要處理跨數(shù)據(jù)庫的事務(wù)處理是通過 JTA 來實(shí)現(xiàn)的,這種方式在該方案中同樣可以實(shí)現(xiàn),朋友們可以試一試。另外,本方案能使用二級(jí)緩存嗎?當(dāng)然可以。對(duì)于 MultiSessionFactory 當(dāng)然沒有任何問題,它通過不同的 sessionFactory 分離開了不同的數(shù)據(jù)源和值對(duì)象,我們可以毫無顧忌地使用。對(duì)于 MultiDataSource 來說,就有點(diǎn)問題了。 MultiDataSource 使多個(gè)數(shù)據(jù)源使用共同的 sessionFactory ,因此它仿佛就是將多個(gè)數(shù)據(jù)源在邏輯上合并為一個(gè)數(shù)據(jù)源。正因?yàn)槿绱耍覀冃枰WC對(duì)于同一個(gè)表在所有數(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)然不會(huì)有問題。但如果你有通過其它方式插入表的操作,你應(yīng)當(dāng)保證這樣的唯一性。另外,對(duì)于查詢的操作,緩存中存放的既可能是數(shù)據(jù)源 A 的數(shù)據(jù),也可能是數(shù)據(jù)源 B 的數(shù)據(jù),因此你應(yīng)當(dāng)對(duì)數(shù)據(jù)有一個(gè)規(guī)劃。對(duì)于表 T 的數(shù)據(jù),哪些應(yīng)當(dāng)插入到數(shù)據(jù)源 A 中,哪些應(yīng)當(dāng)插入到 B 中,應(yīng)當(dāng)有一個(gè)定義。假如是通過不同單位來決定插入哪個(gè)數(shù)據(jù)源,那么在查詢數(shù)據(jù)源 A 的表 T 是,應(yīng)當(dāng)增加條件只查詢數(shù)據(jù)源 A 應(yīng)當(dāng)有的單位而排除調(diào)其它單位。如此這樣,你只要注意到這兩個(gè)問題,你就可以放心大膽地使用二級(jí)緩存。
?
?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
