我們時(shí)常會(huì)遇到一些 web 項(xiàng)目,需要從不同的數(shù)據(jù)源中抓取數(shù)據(jù)來(lái)進(jìn)行分析,而這些數(shù)據(jù)源是有可能變化的,需要用戶(hù)來(lái)進(jìn)行動(dòng)態(tài)的維護(hù)和添加??墒?,大多數(shù)的 web 程序使用了應(yīng)用服務(wù)器或者容器中間件來(lái)管理數(shù)據(jù)源的生命周期,因此數(shù)據(jù)源的變化自然不能夠獨(dú)立于程序,而需要由專(zhuān)業(yè)人士去進(jìn)行維護(hù),必要時(shí)還需要重新發(fā)布程序來(lái)適應(yīng)數(shù)據(jù)源的變化,而且數(shù)據(jù)源的個(gè)數(shù)、數(shù)據(jù)庫(kù)的類(lèi)型也都會(huì)有所限制。
那么怎樣才可以突破以上這些局限,徹底實(shí)現(xiàn)由用戶(hù)遠(yuǎn)程對(duì)數(shù)據(jù)源進(jìn)行維護(hù)和管理的需求呢?本文提出了一個(gè)有效的解決方案,該方案的大體思路如下:將數(shù)據(jù)源的配置信息保存在一個(gè)地址相對(duì)固定的數(shù)據(jù)庫(kù)或本地文件系統(tǒng)中,系統(tǒng)將依據(jù)這些配置信息自動(dòng)生成數(shù)據(jù)源(每當(dāng)數(shù)據(jù)信息發(fā)生變化時(shí),系統(tǒng)將重新生成新數(shù)據(jù)源),然后通過(guò)工廠(chǎng)模式的持久層將這些數(shù)據(jù)源自動(dòng)分配給對(duì)應(yīng)的類(lèi)對(duì)象使用。如此,只需要一個(gè)用戶(hù)界面,來(lái)對(duì)此數(shù)據(jù)源的配置信息進(jìn)行管理,就可以實(shí)現(xiàn)由用戶(hù)自主維護(hù)數(shù)據(jù)源系統(tǒng)的目的了。下文將以一個(gè) Spring+iBATIS 框架的實(shí)例,來(lái)詳細(xì)描述該解決方案的實(shí)現(xiàn)。
Apache iBATIS 是 Clinton Begin 開(kāi)發(fā),現(xiàn)在由 Apache 基金會(huì)支持的用于加快 JDBC 編程的經(jīng)過(guò)泛化的框架,是當(dāng)前 IT 項(xiàng)目中使用很廣泛的一個(gè)半自動(dòng) ORM 框架,區(qū)別于 Hibernate 之類(lèi)的全自動(dòng)框架,iBATIS 對(duì)數(shù)據(jù)庫(kù)的操作擁有更加靈活的控制,對(duì)于那些經(jīng)常需要調(diào)用本地?cái)?shù)據(jù)庫(kù)函數(shù)自定義 SQL 語(yǔ)句,或是喜歡自己優(yōu)化 SQL 執(zhí)行效率的開(kāi)發(fā)者來(lái)說(shuō),iBATIS 是一個(gè)非常不錯(cuò)的選擇。而得到廣泛應(yīng)用的開(kāi)源企業(yè)架構(gòu) Spring Framework,也很好的將 iBATIS 進(jìn)行了集成,使得 iBATIS 在 Spring 中的使用更加便利、快捷。下面是 iBATIS 的一些關(guān)鍵組件:
- SqlMapClient :是 iBATIS 的重要接口,是線(xiàn)程安全的。這個(gè)接口涉及到對(duì) SQL 映射的執(zhí)行和批處理。
- Sqlmap - config.xml :是使用 iBATIS 的起點(diǎn),負(fù)責(zé)把所有的 SQL 映射文件(sqlmap.xml)組合在一起。該配置文件可以告訴 iBATIS 如何連接數(shù)據(jù)庫(kù),以及獲取哪些 SQL 映射文件(sqlmap.xml)。
- sqlmap.xml 包含了我們將要運(yùn)行的 SQL 語(yǔ)句,被 Sqlmap-config.xml 文件所引用。
我們之所以選用 iBATIS 而不是 Hibernate 來(lái)作為本解決方案的開(kāi)發(fā)框架,主要是因?yàn)橄啾?Hibernate 來(lái)說(shuō) iBATIS 更具靈活性,可以更為方便地對(duì)其結(jié)構(gòu)進(jìn)行改寫(xiě)。尤其是在對(duì)不同數(shù)據(jù)庫(kù)結(jié)構(gòu)的封裝方面,iBATIS 更適用于對(duì)實(shí)現(xiàn)相同邏輯的不同數(shù)據(jù)庫(kù)結(jié)構(gòu)的支持。反觀 Hibernate 則需要對(duì)數(shù)據(jù)庫(kù)結(jié)構(gòu)進(jìn)行封裝,這就意味著對(duì)不同的數(shù)據(jù)庫(kù)結(jié)構(gòu)要生成不同的 PO 類(lèi),這會(huì)使開(kāi)發(fā)工作變得繁瑣。當(dāng)然用戶(hù)也可以選擇使用 Hibernate 來(lái)作為框架,其理念是相同的,不同的只是實(shí)現(xiàn)手段。
Spring 通過(guò) DAO 模式,提供了對(duì) iBATIS 的良好支持。
- SqlMapClient :是 iBATIS 中的主要接口,通過(guò) xml 配置文件可以讓 Spring 容器來(lái)管理 SqlMapClient 對(duì)象的創(chuàng)建,Spring 提供了 SqlMapClientFactoryBean 來(lái)生成該對(duì)象。
- SqlMapClientFactoryBean :SqlMapClientFactoryBean 是由 Spring 所提供的,用來(lái)生成 SqlMapClient 對(duì)象的一個(gè)工廠(chǎng)類(lèi)。當(dāng)使用 Spring 配置文件將 SqlMapClientFactoryBean 作為一個(gè) SqlMapClient 的實(shí)現(xiàn)類(lèi)進(jìn)行注入時(shí),Spring 容器將根據(jù)接口里的定義來(lái)調(diào)用其 getObject 方法,最終返回一個(gè) SqlMapClient 接口的實(shí)現(xiàn)類(lèi)。SqlMapClientFactoryBean 生成的對(duì)象擁有兩個(gè)重要屬性,configLocation 屬性用來(lái)確定 sqlmap-config.xml,dataSource 屬性用來(lái)確定數(shù)據(jù)源。
- SqlMapClientDaoSupport :Spring 提供的數(shù)據(jù)庫(kù)操作類(lèi),應(yīng)用程序的持久層 DAO 則可以繼承這個(gè)類(lèi)。SqlMapClientDaoSupport 需要 Spring 為其注入 SqlMapClient 接口的實(shí)現(xiàn)對(duì)象,來(lái)確定使用何種數(shù)據(jù)源和使用何種 sqlmap-config.xml。
如上節(jié)所述可知,如果要使傳統(tǒng)的 Spring+iBATIS 框架支持動(dòng)態(tài)的多數(shù)據(jù)源持久層,則需要對(duì)其進(jìn)行改良。而數(shù)據(jù)源是由 SqlMapClient 對(duì)象的屬性所定義的,所以要想辦法通過(guò)變更 SqlMapClient 接口的實(shí)現(xiàn)對(duì)象來(lái)達(dá)到目的。Spring 是使用 XML 配置文件來(lái)存儲(chǔ) SqlMapClient 對(duì)象的信息,因此只需要能夠根據(jù)數(shù)據(jù)源配置信息來(lái)動(dòng)態(tài)生成該 XML 配置文件,來(lái)實(shí)現(xiàn)對(duì) SqlMapClient 接口的動(dòng)態(tài)注入即可。
?
?
?
?
持久層架構(gòu)的具體處理流程如上圖所示:
1.創(chuàng)建配置文件生成類(lèi) SqlMapClientFactory,當(dāng)應(yīng)用服務(wù)器啟動(dòng)時(shí),Spring 框架將啟動(dòng) SqlMapClientFactory 類(lèi)的 init 方法(每當(dāng)數(shù)據(jù)源配置信息發(fā)生變化時(shí)也重新啟動(dòng)此方法),該方法將讀取數(shù)據(jù)庫(kù)或本地文件系統(tǒng)中存儲(chǔ)的數(shù)據(jù)源配置信息,然后動(dòng)態(tài)的生成 Spring XML 配置文件。在此 XML 配置文件中,根據(jù)不同的數(shù)據(jù)源定義了很多不同的 SqlMapClient 對(duì)象,并定義了其相對(duì)應(yīng)的數(shù)據(jù)源和 sqlmap-config 文件。
2.創(chuàng)建 SqlMapClient 接口的實(shí)現(xiàn)類(lèi) RoutingSqlMapClient,并通過(guò) Spring 將生成的所有 SqlMapClient 對(duì)象以 Map 的結(jié)構(gòu)形式注入到 RoutingSqlMapClient 中,當(dāng)其被調(diào)用時(shí)將根據(jù)要求使用相應(yīng)的 SqlMapClient 實(shí)現(xiàn)對(duì)象來(lái)對(duì) RoutingSqlMapClient 的方法進(jìn)行重寫(xiě)。
3.將 RoutingSqlMapClient 注入到所有繼承了 SqlMapClientDaoSupport 的 DAO 實(shí)現(xiàn)類(lèi)中,DAO 將根據(jù)實(shí)際需求決定使用哪個(gè)數(shù)據(jù)源。
使用 SqlMapClientFactory 類(lèi)生成 XML 配置文件
如上文的描述,第一步應(yīng)該創(chuàng)建 SqlMapClientFactory 類(lèi),并創(chuàng)建一個(gè)用來(lái)生成 SqlMapClient 的 XML 配置文件的方法。然后配置 Spring,使其能在程序啟動(dòng)時(shí)自動(dòng)調(diào)用 SqlMapClient 的該方法。該方法要從本地文件系統(tǒng)或地址相對(duì)固定的數(shù)據(jù)庫(kù)系統(tǒng)中讀取數(shù)據(jù)源配置信息,SqlMapClientFactory 所讀取的數(shù)據(jù)源配置信息的主要字段如下:
注:<hostname> 是數(shù)據(jù)庫(kù)所在的服務(wù)器地址,<portNum> 是數(shù)據(jù)庫(kù)所用服務(wù)端口號(hào),<databaseName> 是數(shù)據(jù)庫(kù)名稱(chēng),<userName> 是數(shù)據(jù)庫(kù)用戶(hù)名,<password> 是該用戶(hù)的密碼
ID Name Connection User Password DBType
00001
|
PROJECTA
|
jdbc:db2://
<hostname>
:
<portNum>
/
<databaseName>
|
<userName>
|
<password>
|
DB2
|
00002
|
PROJECTB
|
jdbc:microsoft:sqlserver://
<hostname>
:
;DatabaseName=
<databaseName>
|
<userName>
|
<password>
|
sqlserver
|
00003
|
PROJECTC
|
jdbc:db2://
<hostname>
:
<portNum>
/
<databaseName>
|
<userName>
|
<password>
|
DB2
|
00004
|
PROJECTD
|
jdbc:db2://
<hostname>
:
<portNum>
/
<databaseName>
|
<userName>
|
<password>
|
DB2
|
?
SqlMapClientFactory 將根據(jù)這些配置信息生成相應(yīng)的 SqlMapClient 對(duì)象的 XML 配置文件。下面詳細(xì)介紹一下該 XML 配置文件的主要構(gòu)成。
1,根據(jù)數(shù)據(jù)源配置信息的 ID 和 Name,生成所有 SqlMapClient 接口實(shí)現(xiàn)對(duì)象的 Map 列表。
<bean id="routingSqlMapClient" class="com.ibm.mbps.tsd.dao.RoutingSqlMapClient"> <property name="targetSqlMapClients"> <map key-type="java.lang.String"> <entry key="PROJECTA" value-ref="sqlmapClient_00001"/> <entry key="PROJECTB" value-ref="sqlmapClient_00002"/> <entry key="PROJECTC" value-ref="sqlmapClient_00003"/> <entry key="PROJECTD" value-ref="sqlmapClient_00004"/> …… </map> </property> </bean> |
?
2,為每個(gè) SqlMapClient 接口實(shí)現(xiàn)對(duì)象創(chuàng)建數(shù)據(jù)源,數(shù)據(jù)源根據(jù)上文的配置信息生成。
<bean id="datasource_00001" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName"> <value>com.ibm.db2.jcc.DB2Driver</value> </property> <property name="url"> <value> jdbc:db2://hostname:portNum/databaseName</value> </property> <property name="username"> <Value>userName</value> </property> <property name="password"> <value>password</value> </property> </bean> |
?
3,為每個(gè) SqlMapClient 對(duì)象注入數(shù)據(jù)源和 sqlmap-config 配置文件,應(yīng)該注意的是該 sqlmap-config 配置文件是針對(duì)某一類(lèi)數(shù)據(jù)源的,比如多個(gè)數(shù)據(jù)源的數(shù)據(jù)庫(kù)類(lèi)型和數(shù)據(jù)庫(kù)內(nèi)容都相同,那么就應(yīng)該使用同一張配置文件。
清單 3. SqlMapClient 對(duì)象配置示例
<bean id=" sqlmapClient_00001" class="org.springframework.orm.ibatis.SqlMapClient FactoryBean"> <property name="configLocation" value="classpath:/sqlmap/db2/sql-map-config.xml"/> <property name="dataSource"> <ref local=" datasource_00001"/> </property> </bean> |
?
重寫(xiě) SqlMapClient 接口的實(shí)現(xiàn)類(lèi) RoutingSqlMapClient
生成了 SqlMapClient 對(duì)象后,我們還要?jiǎng)?chuàng)建一個(gè) RoutingSqlMapClient 的實(shí)現(xiàn)類(lèi)用來(lái)重寫(xiě)相應(yīng)的 SqlMapClient 接口方法。RoutingSqlMapClient 將創(chuàng)建一個(gè) Map 變量去承接上文生成的 SqlMapClient 實(shí)現(xiàn)對(duì)象的 Map 列表,然后根據(jù)關(guān)鍵字來(lái)判斷用哪個(gè)實(shí)現(xiàn)對(duì)象來(lái)動(dòng)態(tài)的重寫(xiě) RoutingSqlMapClient 類(lèi)。RoutingSqlMapClient 使用變量 targetSqlMapClients 來(lái)接收 SqlMapClient 對(duì)象列表。
清單 4. RoutingSqlMapClient 類(lèi)的代碼片段
public class RoutingSqlMapClient implements SqlMapClient { private Map<String, SqlMapClient> targetSqlMapClients; public void flushDataCache() { targetSqlMapClients.get(getDSType()).flushDataCache(); } public SqlMapSession getSession() { return targetSqlMapClients.get(getDSType()).getSession(); } public int delete(String id, Object parameterObject) throws SQLException { return targetSqlMapClients.get(getDSType()).delete(id,parameterObject); } public Object insert(String id, Object parameterObject) throws SQLException { return targetSqlMapClients.get(getDSType()).insert(id,parameterObject); } public List queryForList(String id, Object parameterObject) throws SQLException { return targetSqlMapClients.get(getDSType()).queryForList(id,parameterObject); } …… } |
?
創(chuàng)建繼承 SqlMapClientDaoSupport 的 DAO 類(lèi)
我們選擇使用 RoutingSqlMapClient 重寫(xiě) SqlMapClient 的實(shí)現(xiàn)方法,而不是將 SqlMapClient 實(shí)現(xiàn)對(duì)象直接注入到對(duì)應(yīng) DAO 中的原因是:一個(gè) DAO 類(lèi)有可能對(duì)應(yīng)多個(gè)數(shù)據(jù)源,如果將只包含一個(gè)數(shù)據(jù)源的 SqlMapClient 實(shí)現(xiàn)對(duì)象直接注入 DAO,那將嚴(yán)重限制 DAO 的可重用性。因此我們將整個(gè) SqlMapClient 實(shí)現(xiàn)對(duì)象的列表裝載入 RoutingSqlMapClient 類(lèi),在邏輯層定義使用哪一個(gè) SqlMapClient 對(duì)象對(duì) RoutingSqlMapClient 進(jìn)行重寫(xiě),持久層的 DAO 架構(gòu)如下圖所示:
圖 3. 持久層 DAO 架構(gòu)圖(
查看大圖
)
?
DAO 實(shí)體類(lèi)繼承 SqlMapClientDaoSupport 并實(shí)現(xiàn)相應(yīng)不同的接口。如圖所示,不同的接口對(duì)應(yīng)不同的上層邏輯,而實(shí)現(xiàn)其邏輯的 DAO 實(shí)現(xiàn)類(lèi)引用了不同的數(shù)據(jù)源和 sqlmap-config.xml,這些數(shù)據(jù)源和 XML 定義在 SqlMapClient 對(duì)象內(nèi),當(dāng)我們調(diào)用持久層 DAO 類(lèi)對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作時(shí)需要先調(diào)用 RoutingSqlMapClient 內(nèi)的 setDSType() 方法來(lái)確定使用哪個(gè)數(shù)據(jù)源,并使用對(duì)應(yīng)的 SqlMapClient 實(shí)現(xiàn)對(duì)象對(duì) RoutingSqlMapClient 進(jìn)行重寫(xiě),這樣就可以把對(duì)應(yīng)此數(shù)據(jù)源的 SqlMapClient 以 RoutingSqlMapClient 對(duì)象的形式傳入到 DAO 內(nèi),實(shí)現(xiàn)多數(shù)據(jù)源并存的結(jié)構(gòu)了。
支持動(dòng)態(tài)更新的多數(shù)據(jù)源持久層開(kāi)發(fā)完畢后,還應(yīng)該為用戶(hù)開(kāi)發(fā)一套 UI 組件來(lái)使用戶(hù)能夠?qū)?shù)據(jù)源信息進(jìn)行更新和維護(hù)。而每次用戶(hù)更新完畢都要調(diào)用 SqlMapClientFactory 類(lèi)的 init 方法,來(lái)重新生成 SqlMapClient 的 XML 配置文件,這樣就可以不重新啟動(dòng)服務(wù)器而實(shí)現(xiàn)數(shù)據(jù)源的動(dòng)態(tài)更新和添加了。
本文描述了,可動(dòng)態(tài)更新的多數(shù)據(jù)源持久層系統(tǒng)的一種實(shí)現(xiàn)方式,對(duì)于開(kāi)發(fā)類(lèi)似項(xiàng)目的開(kāi)發(fā)者有一定的借鑒作用。但是由于本文的主要目的是闡述一種理念方法而不是具體實(shí)現(xiàn),所以一些相關(guān)技術(shù)及具體實(shí)現(xiàn)沒(méi)有寫(xiě)出,但開(kāi)發(fā)者可以根據(jù)本文的思路選用自己喜歡的方式來(lái)進(jìn)行實(shí)現(xiàn),此外對(duì)于一些名詞和 iBATIS 及 Spring 的功能沒(méi)有予以詳細(xì)介紹,建議對(duì) iBATIS 架構(gòu)不熟的讀者能夠自行參考其它教程。本文選用 Spring+iBATIS 框架僅作為示例,但僅僅是建議使用,不對(duì)使用結(jié)果和效果做任何保證。(本文內(nèi)容僅代表作者個(gè)人觀點(diǎn))
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

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