現在,越來越多的企業項目需要一種將 Java 對象綁定到關系數據 ? 並且在眾多關系數據庫上進行綁定 ? 的可靠方法。遺憾的是,(許多人已體會到其中的艱難)內部解決方案很難構建,而對其進行長期維護和擴展就更難了。在本文中,BrUCe Snyder 向您介紹了使用 Castor JDO 的基礎知識,Castor JDO 是一種開放源碼數據綁定框架,它恰好基於百分之百純 Java 技術。
Castor JDO(Java 數據對象 (Java Data Objects))是一種開放源碼的、百分之百 Java 數據綁定框架。Castor JDO 最早發布於 1999 年 12 月,它是第一批可用的開放源碼數據綁定框架之一。自那時以來,這項技術已獲得了長足的發展。現在,往往將 Castor JDO 同許多其它技術(既有開放源碼的,也有商業的)結合使用,以將 Java 對象模型綁定到關系數據庫、XML 文檔以及 LDAP 目錄上。
在本文中,您將了解使用 Castor JDO 的基本知識。我們將從關系數據模型和 Java 對象模型開始,然後討論在二者之間進行映射的基礎知識。接下來,我們將討論 Castor JDO 的一些特性。通過使用一個基於產品的簡單示例,您將了解一些基本內容,如(關系的和面向對象的)繼續、從屬與相關關系、Castor 的對象查詢語言(Object Query Language)實現以及 Castor 中短事務和長事務的比較。因為本文只是對 Castor 的簡介,所以我們在這裡將使用非常簡單的示例,而且我們也不對任何一個主題進行深入討論。讀完本文之後,您將對這項技術在整體上有了一個較好的了解,也為將來的探索打下了較好的基礎。
請注重,本文將不深入討論對象-關系映射這一一般性的主題。想了解關於對象-關系映射的更多知識,請參閱參考資料一節。
假如您想了解 Castor JDO 與 Sun JDO 規范之間的差異,那麼請參閱“Castor 與 Sun JDO 規范有何差異?”。
開始
有時我喜歡通過對數據建模來開始項目;而有時我喜歡通過對對象建模來開始項目。針對本文的目的,我們將從數據模型開始。Castor 附帶了一些 JDO 示例,它們給出了圍繞某個產品概念的數據模型和對象模型。我們將使用其中的一個示例,將其貫穿應用於本文的幾個不同階段。圖 1 是該示例數據模型的實體-關系(ER)圖。為簡單起見,該圖不含顯式的外鍵。然而,需要注重的是表之間確實存在標識引用。
獲取源代碼
Castor 項目的源代碼是通過一種稱為 ExoLab 許可證的 BSD 樣式許可證發布到 Java 技術社區的。請參閱參考資料以下載 Castor JDO。
圖 1. Castor JDO 示例數據模型
圖 2 是該示例對象模型的 UML 類圖。JDO 示例提供了表到對象的一對一表示。
圖 2. Castor JDO 示例對象模型
Castor 的 Java 對象很象 JavaBeans 組件。因此,(除非將特性映射成直接訪問)對象的每個特性通常都有一對取值(Accessor)和賦值(mutator)方法(getter/setter)。更加復雜的關系可以含有用於其它目的的額外的邏輯。對象-關系映射可以迅速變得極其復雜;然而,這些示例實際上卻十分簡單。
清單 1 顯示了 Product 對象的源代碼。注:對於 product 表中包括標識在內的每一列,代碼都含有對應的特性。對於剛接觸對象-關系映射的人來說,對應於標識符的特性可能顯得很希奇,但這種構造的確十分常見,而且也在其它對象-關系框架中使用。Castor 在內部使用對象標識來跟蹤對象。此外,還有兩個 java.util.Vector 用於關聯的 ProductDetail 和 Category 對象。還要注重的是,對象包括一個 toString() 方法,它在調試應用程序時可用作日志記錄用途。
清單 1. Product.java
package myapp;
import java.util.Vector;
import java.util.Enumeration;
import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.Persistent;
public class Product implements Persistent
{
private int _id;
private String _name;
private float _price;
private ProductGroup _group;
private Database _db;
private Vector _details = new Vector();
private Vector _categories = new Vector();
public int getId()
{
return _id;
}
public void setId( int id )
{
_id = id;
}
public String getName()
...
public void setName( String name )
...
public float getPrice()
...
public void setPrice( float price )
...
public ProductGroup getGroup()
...
public void setGroup( ProductGroup group )
...
public ProductDetail createDetail()
{
return new ProductDetail();
}
public Vector getDetails()
{
return _details;
}
public void addDetail( ProductDetail detail )
{
_details.add( detail );
detail.setProduct( this );
}
public Vector getCategories()
{
return _categories;
}
public void addCategory( Category category )
{
if ( _categories.contains( category ) ) {
_categories.addElement( category ); category.addProduct( this );
}
}
...
public String toString()
{
return _id + " " + _name;
}
}
在查看圖 2 時,請注重 Product 與 ProductDetail 對象和 Category 對象有不同的關系:
一個 Product 可以包含多個 ProductDetail。這是一對多關系。
多個 Product 可以包含多個 Category 對象。這是多對多關系。
addDetail() 和 addCategory() 方法用於將對象添加到每個 java.util.Vector 以治理它們各自的關系。
映射描述符的因果
正如有些人相信生活的要害是要正確理解並接受個人的命運一樣,我發現使用 Castor JDO 的要害是要正確理解並實現映射描述符。映射描述符提供關系數據庫表和 Java 對象之間的連接(映射)。映射描述符的格式是 XML ? 這是有充分理由的。Castor 項目的另一半是 Castor XML(參閱參考資料)。Castor XML 提供出色的 Java 到 XML 和 XML 到 Java 數據綁定框架。Castor JDO 利用 Castor XML 的能力來將 XML 文檔數據分解到 Java 對象模型,以便讀取映射描述符。
將對象和特性映射成元素和屬性
每個 Java 對象都由一個 <class> 元素表示,而對象中的每個特性都由一個 <field> 元素表示。此外,關系表內的每列都由一個 <sql> 元素表示。清單 2 顯示了上面所見到的 Product 對象的映射。先請看該清單,接下來我們將討論該代碼的一些亮點。
清單 2. Product 的映射
<class name="myapp.Product" identity="id">
<description>Product definition</description>
<map-to table="prod" xml="product" />
<field name="id" type="integer">
<sql name="id" type="integer" />
</field>
<field name="name" type="string">
<sql name="name" type="char" />
</field>
<field name="price" type="float">
<sql name="price" type="numeric" />
</field>
<!-- Product has reference to ProductGroup,
many products may reference same group -->
<field name="group" type="myapp.ProductGroup">
<sql name="group_id" />
</field>
<!-- Product has reference to ProductDetail
many details per product -->
<field name="details" type="myapp.ProductDetail" required="true"
collection="vector">
<sql many-key="prod_id"/>
</field>
<!-- Product has reference to Category with
many-many relationship -->
<field name="categories" type="myapp.Category" required="true"
collection="vector">
<sql name="category_id"
many-table="category_prod" many-key="prod_id" />
</field>
</class>
<class> 元素支持一些重要的屬性和元素。例如,Product 映射使用 identify 屬性來指示對象的哪個特性充當對象標識符。<class> 元素還支持 <map-to> 元素,該元素告訴 Castor 每個對象映射到什麼關系表上。<field> 元素也支持一些屬性。
請注重,所有 <field> 和 <sql> 元素的映射都包含 type 屬性。類型屬性向 Castor 指示:該在內部使用什麼 TypeConvertor 來在對象和關系數據類型之間進行轉換。
定義關系
對於每個“對多”關系,都存在類型屬性的非凡情形。如同以前所提到的那樣,Product 中的兩個 java.util.Vector 處理一對多和多對多關系。一對多關系存在於稱為 details 的 <field> 元素中。details 元素中的信息告訴 Castor:特性是 java.util.Vector 類型的 Collection;Collection 包含 ProductDetail 類型的對象;並且它是 required(即,不能為空值)。<sql> 元素告訴 Castor:<field> 的對象映射含有一個稱為 prod_id 的 SQL 列,該列將被用於標識一對多關系。
多對多關系存在於稱為 categories 的 <field> 元素中。categories 元素告訴 Castor:特性是 java.util.Vector 類型的 Collection;該 Collection 含有 Category 類型的對象;並且它是 required(即,它不能為空值)。<sql> 元素告訴 Castor:該關系利用一個稱為 category_prod 的額外的表。它還聲明該 <field> 的對象映射含有一個稱為 prod_id 的 SQL 列,該列應該同 category_id 一起用來標識多對多關系。
ProductGroup 和 Product 之間還存在另一種關系。該關系是一對多關系,通過這一關系,許多 Product 就能夠與同一個 ProductGroup 之間存在關系。雖然該關系與 Product 和 ProductDetail 之間的一對多關系相同,但其運行卻相反(多對一),因此我們只在映射中看到了關系的“對一”的一方。
Castor 中的繼續
Castor 使用兩種類型的繼續:Java 繼續和關系繼續。清單 3 含有 Computer 的映射。正如您能從圖 1 中回憶起來的一樣,Computer 是 Product 的繼續。
當 Java 對象只是繼續一般基類或實現一個接口時,無須在映射描述符中反映該繼續。然而,因為 Product 和 Computer 都是已映射的類,所以必須為 Castor 注明繼續以正確地反映它。<class> 元素的 extends 屬性用於表示 Computer 和 Product 之間的關系。在圖 1 中請注重 comp(計算機)表並不包括來自 prod(產品)表的列。相反,prod 表包含基本信息,comp 表繼續了該表。
清單 3. Computer 的映射
<class name="myapp.Computer" extends="myapp.Product" identity="id">
<description>Computer definition, extends generic
product</description>
<map-to table="computer" xml="computer" />
<field name="id" type="integer">
<sql name="id" type="integer" />
</field>
<field name="cpu" type="string">
<sql name="cpu" type="char"/>
</field>
</class>
從屬關系 vs. 相關關系
Castor 對兩個對象之間的關系進行了區分,分為從屬或相關,並且對每種類型的關系維護不同的生命周期。至此,我們只討論了獨立(或相關)對象。獨立對象是那些未在映射描述符的 <class> 元素中指定 depends 屬性的對象。假如您想在獨立對象上執行任何 CRUD(創建、讀取、更新和刪除)操作,那麼您可以直接在對象上執行這些操作。
清單 4 包含從屬對象 ProductDetail 的映射。請注重,<class> 元素包含 depends 屬性。這就告訴 Castor:ProductDetail 的所有操作都從屬於 Product。在這裡,Product 是主對象而 ProductDetail 是從屬對象。假如您想在 ProductDetail 對象上執行任何 CRUD 操作,那麼您只能通過其主對象來執行這些操作。也就是,您必須首先獲取一個 Product 並使用取值方法導航到 ProductDetail。每個獨立對象可能只有一個主對象。
清單 4. ProductDetail 的映射
<class name="myapp.ProductDetail" identity="id" depends="myapp.Product">
<description>Product detail</description>
<map-to table="prod_detail" xml="detail" />
<field name="id" type="integer">
<sql name="id" type="integer"/>
</field>
<field name="product" type="myapp.Product">
<sql name="prod_id" />
</field>
<field name="name" type="string">
<sql name="name" type="char"/>
</field>
</class>
在對象模型上執行查詢
Castor 實現了對象查詢語言(OQL)的 ODMG 3.0 規范的一個子集。OQL 的語法類似於 SQL 的語法,但它卻使您能夠查詢對象模型,而不是直接查詢數據庫。在支持多個數據庫時,這可能是一項強大的功能。Castor 的 OQL 實現在內部將 OQL 查詢轉換成用於數據庫的適當的 SQL。使用 bind() 方法將參數綁定到查詢上。以下是 OQL 查詢的一些簡單示例。
Castor 的 OQL 實現並不在整個查詢中繼續使用全限定對象名,相反它支持對象別名的使用。在下面的這些查詢中,c 就是這樣的一個別名。
假如想要查詢以找出所有 Computer,可以執行下列查詢:
SELECT c FROM myapp.Computer c
假如想要查詢以找出標識等於 1234 的 Computer,可以以:
SELECT c FROM myapp.Computer c WHERE c.id= $1
開始,後跟:
query.bind( 1234 )
要查詢名稱與非凡字符串相似的 Computer,可以執行下列查詢:
SELECT c FROM myapp.Computer c WHERE c.name LIKE $1
後跟:
query.bind( "%abcd%" )
要查詢其標識符屬於一列標識符之內的 Computer,可以執行下列查詢:
SELECT c FROM myapp.Computer c WHERE c.id IN LIST ( $1, $2, $3 )
後跟:
query.bind( 97 )
query.bind( 11 )
query.bind( 7 )
對 Castor OQL 實現的更具體討論超出了本文的范圍。要了解更多信息,請參閱參考資料一節中的參考資料。
Castor 中的事務
Castor 的持久性操作在事務上下文內進行。然而,Castor 不是事務治理器。相反,它更象是高速緩存治理器,因為它利用事務的原子性來將對象持久保存到數據庫。這種設置答應應用程序更加輕易地提交或回滾對對象圖的任何更改。運行於非受管環境下的應用程序必須顯式提交或回滾事務。當應用程序服務器運行於受管環境下時,應用程序服務器可以治理事務性上下文。
短事務 vs. 長事務
Castor 中正常的事務被稱為短事務。Castor 還提供了長事務這一概念,長事務由兩個短事務組成。我們可以使用一個典型的 Web 應用程序來理解兩種類型的事務之間的差異。對需要從數據庫讀取數據、將數據顯示給用戶然後把數據提交給數據庫的應用程序來說,我們查看有可能冗長的事務時間(這對於 Web 應用程序是很典型的)。但不能在數據庫中無限地保持寫鎖。為了解決這一問題,Castor 利用了兩個短事務。例如,第一個短事務使用只讀查詢來具體化對象,這讓您可以在不打開事務就顯示對象。假如用戶做了更改,則啟動第二短個事務,update() 方法將更改過的對象帶入事務的上下文。清單 5 是一個長事務的示例。
清單 5. 長事務示例
public LineItem getLineItem()
{
db.begin();
LineItem lineItem = ( LineItem ) db.load( LineItem.class,
lineItemNum, Database.ReadOnly );
db.commit();
return lineItem;
}
public void updateOrder( LineItem li )
{
db.begin();
db.update( li );
db.commit();
}
因為第一個短事務和第二個短事務之間的時間間隔是任意的,所以您將不得不執行一個髒檢查(dirty check)來確定是否在數據庫中更改了對象。髒檢查用於驗證對象在長事務期間沒有被修改。您可以通過指定映射描述符的 <sql> 元素中的 dirty 屬性來啟用髒檢查。為了使髒檢查正確工作,每個對象都必須保持一個時間戳記。假如您實現了 Timestampable 回調接口,Castor 將在第一個短事務中設置時間戳記並在第二個短事務期間檢查該時間戳記。
Database 支持
Castor JDO 的主要特性是:它提供了將 Java 對象綁定到關系數據庫的 API。Castor JDO 支持下列數據庫:
DB2
HypersonicSQL
Informix
InstantDB
Interbase
mysql
Oracle
PostgreSQL
SAP DB
SQL Server
Sybase
Generic(用於一般 JDBC 支持)
添加對其它數據庫的支持十分輕易。參閱參考資料以獲取進一步的信息。
結束語
在本文中,我們討論了使用 Castor 將關系數據模型映射到 Java 對象模型的基礎知識。本文中所使用的示例與 Castor 源代碼中當前所提供的示例非常相近。這些簡單的示例肯定不能涵蓋 Castor 的全部能力。實際上,Castor 支持許多其它特性,包括鍵生成、延遲裝入、LRU 高速緩存、不同的鎖定/訪問方式以及更多。假如您想了解關於這些特性以及許多其它特性的更多信息,請參考參考資料一節。
Castor JDO 只不過是當今開放源碼世界中許多可用數據綁定解決方案中的一種。如本文所顯示的那樣,Castor 為內部解決方案提供了完善的替代方案,內部解決方案通常需要您為您支持的每個數據庫維護不同的 SQL 和 JDBC 代碼。而且,因為 Castor 是開放源碼的、基於社區的項目,所以 Castor 得到了不斷的改進。我鼓勵您研究這一技術,甚至可能成為 Castor 社區的一員。