簡介
正如 W3C 推薦標准 所指出的,XML 的一些設計目標是針對語言的應用程序開發方面:
“XML 將會支持各種各樣的應用程序。”
“編寫處理 XML 文檔的程將會很容易。”
其他目標(比如可讀性、序列化和傳輸)得到了許多關注,但是應用程序開發目標沒有得到同樣的關注。
本文是本系列的第一篇,本系列從三個層面討論 XML 對應用程序開發的影響:
第 1 部分討論在哪些情況下在應用程序開發中使用 XML 可以使應用程序開發過程更簡單、成本更低、更可移植和質量更高。在這十年中,XML 編程將使應用程序開發方式發生根本性變化,這種變化甚至可能與面向對象方法在過去十年中的影響相當。
第 2 部分主要討論數據庫的角色。重點是 DB2 9(原來的代碼號是 Viper)和 Viper 2 功能。將了解:
新的 XML 存儲和查詢環境如何操作應用程序層的 XML 數據模型
采用新的基於 XML 的應用程序開發體系結構之後,數據庫模式如何變得更簡單更自然
如何按照在應用程序中查詢數據的相同方式查詢數據庫中的 XML 數據
最後,如何結合關系數據和 XML 數據,從而同時獲得這兩個環境的優勢。
第 3 部分主要關注客戶機,介紹在 Web 浏覽器和 Web 服務器中使用的 XML 技術:AJax、XSLT、SVG、Comet、feed 和 mashup。學習如何在數據庫中生成 feed 和 Web 服務,在應用程序層查詢和組合它們,然後在客戶機浏覽器中顯示它們。
第四篇文章將所有這些技術結合在一起,並展示一個真實的示例。
XML 數據模型基本知識
在傳統上,XML 用來定義業務文檔的元數據。Document Object Model(DOM)用來在應用程序中操作這些元數據。如果我們查看 DOM,就會看到它為層次化 XML 數據結構提供一個對象接口,可以用 DOM API 操作這個層次結構。換句話說,DOM 可以作為對象包裝器操作任何可以用 XML 表示的數據結構。
在 XML 數據模型中,將許多應用程序數據對象定義為 XML。因為 XML 是層次化的,所以很容易以自然的、人可讀的格式捕捉不同數據對象之間的關系。
注意:
如果數據已經是 XML 格式的,過程的其余部分就很自然很簡單了。如果數據是關系格式,仍然可以使用這種方法,但是需要在關系數據和 XML 數據之間建立雙向映射。這個步驟可以使用 SQL/XML 發布和分解函數來實現。大多數關系數據庫支持這些函數和其他映射技術。對於在應用程序開發中只使用 XML 作為數據模型的許多開發人員,關系數據到層次化數據(XML)的映射似乎是不必要的。但是作為開發人員,在將關系映射到數據對象時,我們總是這麼做。使用 XML 數據模型的優點可能足以促使許多開發人員考慮在應用程序開發中使用 XML,即使在他們的業務模型中並不需要 XML 數據。
定義了 XML 數據模型之後,可以使用 DOM 解析器在應用程序中實例化 XML 數據模型。為了隔離應用程序代碼與用來導航和操作 XML 模型的 DOM API,可以為 DOM 和 XPath 實現創建一個包裝器。這個包裝器也會使應用程序代碼的可移植性更好。
本文提供了 一個 Java 包裝器示例,您可以直接使用它,也可以用它作為模板建立自己的包裝器。應用程序的業務邏輯使用包裝器 API 直接操作 XML 模型。修改後的 XML 數據可以輕松地序列化,並在不同對象或多層環境(SOA)的不同層之間傳遞。
-XML 數據模型與數據對象模型
大多數應用程序由業務對象組成,業務對象操作數據對象的層次結構。數據對象一般是業務數據的簡單包裝器。它們的主要用途是以受控制的方式向業務對象公開封裝的數據。使用對象包裝器的另一個好處是,它們可以以自然的對象層次結構表達關系表中存儲的數據,從而捕捉數據之間的關系。編程工作的一個主要部分就是為應用程序業務數據創建這些對象包裝器。
因為 XML 本質上維護數據結構之間的關系,所以不需要創建單獨的對象層次結構來捕捉各個數據結構之間的關系。另外,XML 已經有一個標准的對象模型,Document Object Model(DOM)。這個模型的實現處理 XML 數據的構造、修改和序列化。通過結合使用 XPath 和 DOM API,很容易在業務應用程序中裝載、修改和保存 XML 數據。
一個真實示例
為了更好地理解這兩個模型之間的差異,我們來看看每個模型如何影響應用程序的設計和實現。
我將使用一個簡單的場景演示這兩種方式,這個場景要處理客戶和訂單信息。
數據對象模型
按照數據對象模型方式,首先需要創建包裝器對象來封裝客戶和訂單數據,見清單 1、清單 2 和清單 3。
清單 1. 創建客戶
public class Customer
{
int customerid;
String firstname;
String lastname;
Items itemspurchased ;
Public Customer (int custid, Connection conn)
{
Statement dbstmt= conn.createStatement();
ResultSet dbResult = dbstmt.executeQuery("select fname, lname from
customer_table where customerid=custid");
customerid=custid;
SetFirstName(dbResult.getString(1));
SetLastName(dbResult.getString(2));
}
public String GetFirstName {return firstname;}
public Void SetFirstName (fname) {firstname=fname;}
public String GetLastName {return lastname ;}
public Void SetLastName (lname) {lastname=lname;}
public Items GetItemsList {return itemspurchased; }
public SetItemsList (list) { itemspurchased =list;}
}
清單 2. 創建 Items 類
public class Items
{
Hashtable list=new Hashtable();
Public Items(int custid,Connection conn)
{
Statement dbstmt= conn.createStatement();
ResultSet dbResult = dbstmt.executeQuery("select itemid, description,
price, date from purchase_table where customerid=custid")
While (dbResult.rs.next ())
{
tempitem = new Item();
tempitem.SetID(dbResult. getString(1));
tempitem. SetDescription (dbResult. getString(2));
tempitem. Setprice (dbResult. getFloat(3));
tempitem. SetpurchaseDate (dbResult. getString(4));
Additem (tempitem);
}
}
public void AddItem (item oneitem) {list.put(oneitem.GetID(),oneitem);}
public Item GetItem (ItemID) {return list.get(String itemID);}
public Hashtable GetItems(){return list;}
public Items FindItemByPrice (flaot min, float max)
{
Items retList=new Items();
for (Enumeration e=list.elements () ; e.hasMoreElements() ; )
{
item tmpItem=(item)e.nextElement();
float price= tmpItem .GetPrice();
if(price >= min && price <=max)
{
retList.AddItem(tmpItem);
}
}
}
public Items FindItemByDate (purchaseDate) { }
}
清單 3. 創建 Item 定義
public class Item
{
String id;
String description;
String purchaseDate;
Float price;
Public void SetID (String ItemID) {id= ItemID;}
Public void SetDescription (String desc) { description = desc;}
Public void SetpurchaseDate (String pDate) { purchaseDate = pDate ;}
Public void Setprice (float pprice { price = pprice ;}
Public String GetID (){return id;}
Public String GetDescription(){return description;}
Public float GetPrice(){return price;}
}
現在可以在應用程序中使用這些數據對象來管理底層數據。
清單 4. 在應用程序中操作數據對象
Customer customer = new Customer (custid,dbConnection)
customer.SetItemList (new Items(custid , dbConnection)) ;
Items list=customer.GetItemsList(). FindItemByPrice(15.0,25.50);
for (Enumeration e=list.elements () ; e.hasMoreElements() ; )
{
System.out.println(((item)e.nextElement()).GetDescription());
}
在上面的示例中,我們發現數據對象的代碼比業務邏輯需要的代碼多得多。另外,因為包裝器對象隱藏了底層業務數據之間的關系,所以包裝器對象 API 必須有良好的文檔記錄,應用程序開發人員才能了解如何正確地使用它們。
在數據對象模型中,很容易在對象層次結構之間進行簡單的導航,但是必須為每個搜索條件實現高級搜索和導航功能(例如 FindItemByPrice)。
XML 數據模型
因為包裝器的主要用途是封裝業務數據,所以可以用 XML 數據模型替換它們。
清單 5. XML 數據模型
<Customer customerid ="" firstname="" lastname="" >
<Items>
<Item ID="" description="" purchaseDate="" price="" />
</Items>
</Customer>
現在,假設數據庫中的數據已經存儲為 XML 格式:
客戶數據存儲為
商品數據存儲為
那麼,對於任何給定的客戶,我們只需獲取客戶 XML 並在其中插入查詢的商品列表。
重寫應用程序代碼,讓它使用 XML 模型保存客戶和商品信息。為了創建和操作這個 XML 數據模型的實例,我們將使用 DOM 包裝器類 XMLParse(見 下載 一節)。
第一種情況 —— 數據在數據庫中存儲為 XML 格式
清單 6. 重寫應用程序來使用 XML 模型
1. Statement dbstmt= conn.createStatement();
2. ResultSet dbResult = dbstmt.executeQuery("select custXML from
customer_table where customerid=custid");
3. XMLParse customerXML = new XMLParse(dbResult. getString(1));
4. customerXML.appendNode("/Customer", customerXML.createNode ("<Items/>"))
5. dbResult = dbstmt.executeQuery("select itemXML from purchase_table
where customerid=custid");
6. While (dbResult.rs.next ()) {
7. Node itemnode= customerXML.createNode (dbResult. getString(1));
8. customerXML.appendNode(itemnode ,"/Customer/Items",false);
}
9. customerXML.find("/Customer/Items/item[@price>15.0 and @price <25.5]",true);
10. for(int i=0;i < customerXML.currentFind.getLength();i++) {
11. System.out.println(customerXML.getValue("@description",i));
}
第一個查詢(第 2 行)返回給定客戶的 custXML 列中的 XML 數據。將這個 XML 字符串傳遞給 DOM 包裝器的構造器(第 3 行),DOM 包裝器進而使用 XML 解析器實例化一個代表 XML 數據的對象層次結構。
注意:因為客戶 XML 中還沒有任何 Items 元素(這符合我們在模型中定義的 XML 模式),所以我們創建一個新的 Items 元素(第 4 行),並把它作為子元素追加到 Customer 元素中。
第二個查詢(第 5 行)從數據庫中獲取這位客戶購買的商品的列表。將列表中的每個商品(第 7 行)追加到 DOM 對象層次結構中 Customer/items 路徑的下面(第 8 行)。
最後,使用 XPath 在 DOM 對象層次結構中搜索給定價格范圍內的所有商品(第 9 行),並輸出搜索到的每個商品的說明(第 10 行)。
第二種情況 —— 所有數據在數據庫中存儲為關系形式
因為數據沒有存儲為 XML 格式,所以需要在查詢中使用 SQL/XML 發布函數執行轉換。
清單 7. 使用 SQL/XML 發布函數執行轉換
1. Statement dbstmt= conn.createStatement();
2. ResultSet dbResult = dbstmt.executeQuery("select XMLelement( name "Customer" ,
XMLattributes(customerid as "customerid" ),
XMLattributes(fname as "firstname" ),
XMLattributes(lname as "lastname" )
) from customer_table where customerid=custid");
3. XMLParse customerXML = new XMLParse(dbResult. getString(1));
5. dbResult = dbstmt.executeQuery("select XMLelement( name "items" ,
XMLelement( name "item" ,
XMLattributes(itemid as "id" ),
XMLattributes(description as "description" ),
XMLattributes(price as "price" ),
XMLattributes(date as "purchaseDate" )
)
) from purchase_table where customerid=custid");
6. if (dbResult.rs.next ()) {
7. Node itemsnode= customerXML.createNode (dbResult. getString(1));
8. customerXML.appendNode(itemsnode ,"/Customer",false);
}
9. customerXML.find("/Customer/Items/item[@price>15.0 and @price <25.5]",true);
10. for(int i=0;i < customerXML.currentFind.getLength();i++) {
11. System.out.println(customerXML.getValue("@description",i));
}
所以,即使數據庫中沒有 XML,仍然可以用 SQL 創建關系數據的 XML 視圖。另外,在查詢中生成商品 XML 的同時,我們添加了外層的 Items 元素。現在,只需將商品 XML 添加到客戶 XML 中。應用程序的其余部分是相同的。
與純對象模型相比使用 XML 模型的好處
數據對象包裝器在應用程序代碼中占很大的比例,這會大大分散開發人員對管理數據對象的業務邏輯的注意力。另外,這些多余的代碼會導致:
額外的成本
更多的 bug
更長的應用程序開發周期
難以移植的代碼
當數據模式中發生任何修改時,需要修改或重新生成對象層次結構
代碼更難維護
沒有內置的數據檢驗功能
需要有更多的文檔來解釋包裝器對象
對於對象層次結構中的高級搜索和導航等功能,需要相當復雜的邏輯實現
由每個業務對象處理數據序列化
如果使用工具進行映射,應用程序就被限制在這種工具上,難以更換工具
通過使用 XML 編程方法,就可以取消整個包裝器對象層次結構,讓開發人員將注意力集中在業務邏輯上,而不會為業務數據結構分心。XML 可以給應用程序開發帶來以下好處:
減少代碼,從而提高質量、降低成本並提高靈活性。
促進 RAD 開發。
XPath 解析器已經內置了高級搜索和導航功能。
XML 模型內置了約束檢查和模式檢驗功能。
模型內置了持久化功能。任何時候都可以把 XML 數據層次結構存儲到文件、字符串或流中。
不需要額外的工具。
這種方法向業務邏輯公開了關系和數據層次結構。在業務對象代碼中,很難了解要操作的業務數據結構的格式(也就是,對業務代碼隱藏了數據模型)。在關系環境中,這是必要的;但是在 XML 環境中,這可能是一個缺點。
業務邏輯代碼容易理解,因為 XPath 描述了數據的性質以及數據與業務結構的關系。
采用 XML 模型時所涉及的問題和解決方案
如果關系數據沒有存儲為 XML 格式,就必須把它們映射到 XML。盡管大多數關系數據庫廠商都提供了相關工具,但是映射過程非常繁瑣。但是,由於 DB2 和 Microsoft® SQL Server 等數據庫服務器引入了純 XML 功能,將 XML 數據分解並映射到關系表不再是必需的。通過使用 XQuery 和 XML 索引,現在可以在應用程序中按原樣搜索和獲取存儲的 XML 數據,采用的方式與從數據庫獲取大字符對象相同。
對於在數據庫中存儲為純 XML 的數據,需要了解如何用 SQL/XML 函數和 XQuery 執行查詢。應該先學習簡單的 XPath 搜索,然後使用復雜的 XQuery,這樣比較容易掌握 XML 查詢。
了解並精通 DOM API 及其實現,以及掌握如何使用 XPath 導航和搜索 XML 層次結構,可能有點兒難度。可以使用 本文附帶的 helper 類 減少對 DOM API 的直接調用。這個 helper 類封裝了 DOM API 並公開更自然的 API,可以從應用程序代碼調用這些 API。它提供了實例化和序列化 XML 模型以及在 XML 實例中搜索和修改數據或元數據所需的所有功能。包裝器類還在必要時處理 XSL 轉換、名稱空間和模式檢驗。
在應用程序的業務邏輯中直接嵌入 DOM API 是一種低效率的做法,因為對 XML 模式的任何修改都要求對應用程序代碼做大量修改。使用許多 API 調用在層次結構中導航;這會降低代碼的可讀性。導航或修改的數據對象不如使用用戶定義的對象包裝器時那麼明顯。包裝器類避免了在業務邏輯中嵌入 DOM API 調用。因為這個包裝器類使用 XPath 導航 DOM,所以如果對模式的任何修改影響到應用程序代碼,那麼只需修改應用程序中受影響的包裝器 API 調用中的 XPath 字符串。另外,因為 XPath 指出了要操作的 XML 節點在層次結構中的位置,所以應用程序代碼的可讀性非常高。
結束語
業務應用程序主要關注創建、操作、存儲和表示業務數據。為業務數據建立數據對象包裝器是為了讓業務邏輯更容易處理業務數據。但是,創建和維護這些數據對象包裝器的成本很高,會使開發人員過分關注數據處理邏輯,而分散了對業務邏輯的注意力。
通過使用 XML 數據模型,就可以取消整個數據對象包裝器層次結構,讓開發人員將注意力集中在業務邏輯上,而不會為業務數據管理分心。通過使用 DOM 包裝器類,應用程序代碼可以與 DOM API 隔離開。使用 XPath 進行導航可以明確指出操作的業務數據中的關系,使應用程序代碼更容易理解。
在理想情況下,數據應該在數據庫中存儲為純 XML;但是即使數據存儲在關系表中,仍然可以在應用程序中先將數據轉換為 XML,然後再處理數據,這種做法在許多情況下是有意義的。
如果包裝在對象層次結構中的數據結構可以用 XML 進行格式化,而且對象層次結構的主要用途是操作這些數據結構並向業務邏輯公開它們,那麼可以考慮用 DOM 替代包裝器對象層次結構。