基於 Spring 和 iBATIS 的動態可更新多資料來源持久層

來源:互聯網
上載者:User

前言

我們時常會遇到一些 web 項目,需要從不同的資料來源中抓取資料來進行分析,而這些資料來源是有可能變化的,需要使用者來進行動態維護和添加。可是,大多數的 web 程式使用了應用伺服器或者容器中介軟體來管理資料來源的生命週期,因此資料來源的變化自然不能夠獨立於程式,而需要由專業人士去進行維護,必要時還需要重新發布 程式來適應資料來源的變化,而且資料來源的個數、資料庫的類型也都會有所限制。

那麼怎樣才可以突破以上這些局限,徹底實現由使用者遠程對資料來源進行維護和管理的需求呢?本文提出了一個有效解決方案,該方案的大體思路如 下:將資料來源的配置資訊儲存在一個地址相對固定的資料庫或本地檔案系統中,系統將依據這些配置資訊自動產生資料來源(每當資料資訊發生變化時,系統將重新生 成新資料來源),然後通過原廠模式的持久層將這些資料來源自動分配給對應的類對象使用。如此,只需要一個使用者介面,來對此資料來源的配置資訊進行管理,就可以實 現由使用者自主維護資料來源系統的目的了。下文將以一個 Spring+iBATIS 架構的執行個體,來詳細描述該解決方案的實現。

回頁首

相關技術介紹

iBATIS

Apache iBATIS 是 Clinton Begin 開發,現在由 Apache 基金會支援的用於加快 JDBC 編程的經過泛化的架構,是當前 IT 項目中使用很廣泛的一個半自動 ORM 架構,區別於 Hibernate 之類的全自動架構,iBATIS 對資料庫的操作擁有更加靈活的控制,對於那些經常需要調用本機資料庫函數自訂 SQL 陳述式,或是喜歡自己最佳化 SQL 執行效率的開發人員來說,iBATIS 是一個非常不錯的選擇。而得到廣泛應用的開源企業架構 Spring Framework,也很好的將 iBATIS 進行了整合,使得 iBATIS 在 Spring 中的使用更加便利、快捷。下面是 iBATIS 的一些關鍵組件:

  • SqlMapClient:是 iBATIS 的重要介面,是安全執行緒的。這個介面涉及到對 SQL 映射的執行和批處理。
  • Sqlmap-config.xml:是使用 iBATIS 的起點,負責把所有的 SQL 對應檔(sqlmap.xml)組合在一起。該設定檔可以告訴 iBATIS 如何串連資料庫,以及擷取哪些 SQL 對應檔(sqlmap.xml)。
  • sqlmap.xml包含了我們將要啟動並執行 SQL 陳述式,被 Sqlmap-config.xml 檔案所引用。

圖 1.iBATIS 架構圖

我們之所以選用 iBATIS 而不是 Hibernate 來作為本解決方案的開發架構,主要是因為相比 Hibernate 來說 iBATIS 更具靈活性,可以更為方便地對其結構進行改寫。尤其是在對不同資料庫結構的封裝方面,iBATIS 更適用於對實現相同邏輯的不同資料庫結構的支援。反觀 Hibernate 則需要對資料庫結構進行封裝,這就意味著對不同的資料庫結構要產生不同的 PO 類,這會使開發工作變得繁瑣。當然使用者也可以選擇使用 Hibernate 來作為架構,其理念是相同的,不同的只是實現手段。

Spring 對 iBATIS 的支援

Spring 通過 DAO 模式,提供了對 iBATIS 的良好支援。

  • SqlMapClient:是 iBATIS 中的主要介面,通過 xml 設定檔可以讓 Spring 容器來管理 SqlMapClient 對象的建立,Spring 提供了 SqlMapClientFactoryBean 來產生該對象。
  • SqlMapClientFactoryBean:SqlMapClientFactoryBean 是由 Spring 所提供的,用來產生 SqlMapClient 對象的一個工廠類。當使用 Spring 設定檔將 SqlMapClientFactoryBean 作為一個 SqlMapClient 的實作類別進行注入時,Spring 容器將根據介面裡的定義來調用其 getObject 方法,最終返回一個 SqlMapClient 介面的實作類別。SqlMapClientFactoryBean 產生的對象擁有兩個重要屬性,configLocation 屬性用來確定 sqlmap-config.xml,dataSource 屬性用來確定資料來源。
  • SqlMapClientDaoSupport:Spring 提供的資料庫操作類,應用程式的持久層 DAO 則可以繼承這個類。SqlMapClientDaoSupport 需要 Spring 為其注入 SqlMapClient 介面的實現對象,來確定使用何種資料來源和使用何種 sqlmap-config.xml。

回頁首

持久層的架構和設計

如上節所述可知,如果要使傳統的 Spring+iBATIS 架構支援動態多資料來源持久層,則需要對其進行改良。而資料來源是由 SqlMapClient 對象的屬性所定義的,所以要想辦法通過變更 SqlMapClient 介面的實現對象來達到目的。Spring 是使用 XML 設定檔來儲存 SqlMapClient 對象的資訊,因此只需要能夠根據資料來源配置資訊來動態產生該 XML 設定檔,來實現對 SqlMapClient 介面的動態注入即可。

圖 2. 持久層架構流程圖(查看大圖)

持久層架構的具體處理流程如所示:

1.建立設定檔產生類 SqlMapClientFactory,當應用伺服器啟動時,Spring 架構將啟動 SqlMapClientFactory 類的 init 方法(每當資料來源配置資訊發生變化時也重新啟動此方法),該方法將讀取資料庫或本地檔案系統中儲存的資料來源配置資訊,然後動態產生 Spring XML 設定檔。在此 XML 設定檔中,根據不同的資料來源定義了很多不同的 SqlMapClient 對象,並定義了其相對應的資料來源和 sqlmap-config 檔案。

2.建立 SqlMapClient 介面的實作類別 RoutingSqlMapClient,並通過 Spring 將產生的所有 SqlMapClient 對象以 Map 的結構形式注入到 RoutingSqlMapClient 中,當其被調用時將根據要求使用相應的 SqlMapClient 實現對象來對 RoutingSqlMapClient 的方法進行重寫。

3.將 RoutingSqlMapClient 注入到所有繼承了 SqlMapClientDaoSupport 的 DAO 實作類別中,DAO 將根據實際需求決定使用哪個資料來源。

回頁首

持久層的具體實現

使用 SqlMapClientFactory 類產生 XML 設定檔

如上文的描述,第一步應該建立 SqlMapClientFactory 類,並建立一個用來產生 SqlMapClient 的 XML 設定檔的方法。然後配置 Spring,使其能在程式啟動時自動調用 SqlMapClient 的該方法。該方法要從本地檔案系統或地址相對固定的資料庫系統中讀取資料來源配置資訊,SqlMapClientFactory 所讀取的資料來源配置資訊的主要欄位如下:

註:<hostname> 是資料庫所在的伺服器位址,<portNum> 是資料庫所用服務連接埠號碼,<databaseName> 是資料庫名稱,<userName> 是資料庫使用者名稱,<password> 是該使用者的密碼

表 1 資料來源配置資訊

ID Name Connection User Password DBType
00001 PROJECTA jdbc:db2://<hostname>:<portNum>/<databaseName> <userName> <password> DB2
00002 PROJECTB jdbc:microsoft:sqlserver://<hostname>:
<portNum>
;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 將根據這些配置資訊產生相應的 SqlMapClient 對象的 XML 設定檔。下面詳細介紹一下該 XML 設定檔的主要構成。

1,根據資料來源配置資訊的 ID 和 Name,產生所有 SqlMapClient 介面實現對象的 Map 列表。

清單 1. routingSqlMapClient 配置

   <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,為每個 SqlMapClient 介面實現對象建立資料來源,資料來源根據上文的配置資訊產生。

清單 2. data source 配置樣本

   <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,為每個 SqlMapClient 對象注入資料來源和 sqlmap-config 設定檔,應該注意的是該 sqlmap-config 設定檔是針對某一類資料來源的,比如多個資料來源的資料庫類型和資料庫內容都相同,那麼就應該使用同一張設定檔。

清單 3. SqlMapClient 對象配置樣本

   <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>  

重寫 SqlMapClient 介面的實作類別 RoutingSqlMapClient

產生了 SqlMapClient 對象後,我們還要建立一個 RoutingSqlMapClient 的實作類別用來重寫相應的 SqlMapClient 介面方法。RoutingSqlMapClient 將建立一個 Map 變數去承接上文產生的 SqlMapClient 實現對象的 Map 列表,然後根據關鍵字來判斷用哪個實現對象來動態重寫 RoutingSqlMapClient 類。RoutingSqlMapClient 使用變數 targetSqlMapClients 來接收 SqlMapClient 對象列表。

清單 4. RoutingSqlMapClient 類的程式碼片段

   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);   }  ……  }  

建立繼承 SqlMapClientDaoSupport 的 DAO 類

我們選擇使用 RoutingSqlMapClient 重寫 SqlMapClient 的實現方法,而不是將 SqlMapClient 實現對象直接注入到對應 DAO 中的原因是:一個 DAO 類有可能對應多個資料來源,如果將只包含一個資料來源的 SqlMapClient 實現對象直接注入 DAO,那將嚴重限制 DAO 的可重用性。因此我們將整個 SqlMapClient 實現對象的列表裝載入 RoutingSqlMapClient 類,在邏輯層定義使用哪一個 SqlMapClient 對象對 RoutingSqlMapClient 進行重寫,持久層的 DAO 架構如所示:

圖 3. 持久層 DAO 架構圖(查看大圖)

DAO 實體類繼承 SqlMapClientDaoSupport 並實現相應不同的介面。,不同的介面對應不同的上層邏輯,而實現其邏輯的 DAO 實作類別引用了不同的資料來源和 sqlmap-config.xml,這些資料來源和 XML 定義在 SqlMapClient 對象內,當我們調用持久層 DAO 類對資料庫進行操作時需要先調用 RoutingSqlMapClient 內的 setDSType() 方法來確定使用哪個資料來源,並使用對應的 SqlMapClient 實現對象對 RoutingSqlMapClient 進行重寫,這樣就可以把對應此資料來源的 SqlMapClient 以 RoutingSqlMapClient 對象的形式傳入到 DAO 內,實現多資料來源並存的結構了。

支援動態更新的多資料來源持久層開發完畢後,還應該為使用者開發一套 UI 組件來使使用者能夠對資料來源資訊進行更新和維護。而每次使用者更新完畢都要調用 SqlMapClientFactory 類的 init 方法,來重建 SqlMapClient 的 XML 設定檔,這樣就可以不重新啟動伺服器而實現資料來源的動態更新和添加了。

回頁首

總結

本文描述了,可動態更新的多資料來源持久層系統的一種實現方式,對於開發類似項目的開發人員有一定的借鑒作用。但是由於本文的主要目的是闡述一 種理念方法而不是具體實現,所以一些相關技術及具體實現沒有寫出,但開發人員可以根據本文的思路選用自己喜歡的方式來進行實現,此外對於一些名詞和 iBATIS 及 Spring 的功能沒有予以詳細介紹,建議對 iBATIS 架構不熟的讀者能夠自行參考其它教程。本文選用 Spring+iBATIS 架構僅作為樣本,但僅僅是建議使用,不對使用結果和效果做任何保證。(本文內容僅代表作者個人觀點)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.