exolab旗下的castor是目前流行的、開放源碼的JDO實現包。它主要用來實現O/R Mapping。運用該軟件包可以大大減輕程序員在處理對象-關系數據庫 的負擔。本文介紹了Castor的基本用法, 並用有大量代碼實例進行解釋。
內容提要:
· 打開JDO數據庫 Client應用
· J2EE應用
· 使用JDO數據庫對象
· 區別瞬時JDO對象和持久JDO對象
· OQLQuery
· 創建與更新和刪除
· 使用JDO和XML
打開JDO數據庫
Castor JDO支持兩種類型的環境,Client應用和J2EE服務器。Client應用被用來負責配置數據庫連接和明確地管理事務。J2EE應用使用JNDI來獲得預先設好的數據庫連接並利用UserTransaction或容器管理事務(CMT)來處理事務。如果你曾經在這兩種環境中使用過JDBC,那麼應該比較熟悉這兩種模型和他們之間的區別。
Client應用
Client應用負責定義JDO數據庫配置,和明確地管理事務處理。數據庫通過一個單獨的XML文件被配置 並連接到一個Mapping文件。在例子代碼中我將數據庫文件命名為database.xml,但是你可以使用任何別的名稱。更多信息參見Castor JDO數據庫配置。
org.exolab.castor.jdo.JDO定義數據庫名稱和屬性並且被用來打開數據庫連接。在上
可以通過設置setConfiguration文件的URL來要求JDO層裝載哪個數據庫配置。注意:Castor JDO在建立多個用同樣的配置的JDO對象的情況下,將會僅僅只執行一次裝載數據庫配置。
org.exolab.castor.jdo.Database對象代表數據庫的一個打開的連接。
線程 JDO對象定義不是線程安全的,因此不應該在並發多線程中使用JDO對象。還有,建立多個JDO對象僅僅 需要少量額外工作,而JDBC連接在每個事務處理中僅僅打開一次。這樣處理的模式能夠大大提高性能。
下列的代碼片斷展示了在Client應用中很常用的組合:“打開數據庫,執行SQL,關閉數據庫。”
JDO jdo;
Database db;
// 定義JDO對象
jdo = new JDO();
jdo.setDatabaseName( "mydb" );
jdo.setConfiguration( "database.xml" );
jdo.setClassLoader( getClass().getClassLoader() );
// 打開一個新的數據庫連接
db = jdo.getDatabase();
// 開始處理事務
db.begin();
// 以下是一些業務邏輯
. . .
// 提交事務處理,並且關閉數據庫
db.commit();
db.close();
J2EE應用
前提: 假設我們的J2EE容器內嵌支持Castor。
J2EE應用依賴於J2EE容器(Servlet,EJB,等等)構成數據庫關連和使用JNDI找到它,使用它。J2EE應用模型允許應用部署從一個中央的地方構成數據庫中心,並且提供了J2EE容器能夠管理橫跨多重的數據源的分布式的處理能力。
在J2EE環境中,應用程序使用JNDI lookup代替org.exolab.castor.jdo.JDO 來構造JDO對象。專家一般推薦把JDO對象的JNDI放在java:comp/env/jdo 之下的命名空間,這樣可兼容JDBC資源列表的規定。
下列的代碼片斷使用JNDI來查詢數據庫對象,並且使用UserTransaction來管理事務處理
InitialContext ctx;
UserTransaction ut;
Database db;
// 用JNDI的查詢得到databse對象
ctx = new InitialContext();
db = (Database) ctx.lookup( "java:comp/env/jdo/mydb" );
// 開始處理事務
ut = (UserTransaction) ctx.lookup( "java:comp/UserTransaction" );
ut.begin();
// 以下是一些業務邏輯
. . .
// 提交事務處理,並且關閉數據庫
ut.commit();
db.close();
如果事務是由容器來管理, 比如 EJB Bean在特別是實體 Bean中時,不需要明確地與指明事務的開始和結束提交;應用服務器將會照料那些正在進行的處理的事務,並在適當的時間的提交或會滾數據庫請求下列的代碼片斷展示了如何利用依賴於容器管理事務:
InitialContext ctx;
UserTransaction ut;
Database db;
// 用JNDI的查詢得到databse對象
ctx = new InitialContext();
db = (Database) ctx.lookup( "java:comp/env/jdo/mydb" );
// 業務邏輯
. . .
// 關閉數據庫
db.close();
使用JDO數據庫對象
區別瞬時JDO對象和持久JDO對象
所有JDO操作在事務處理的上下文之內發生。
JDO通過把數據庫中的數據裝載到內存中的對象,並允許應用程序修改對象,然後存儲對象的新的狀態到數據庫中去當應用程序提交事務時。
所有對象只可能有兩種狀態: 瞬時或者持久(Transient / Persistent)
瞬時: 瞬時JDO對象,當應用程序提交事務時,其狀態將不被保存到的數據庫。瞬時JDO對象的變化將不在數據庫中反映出來。
持久:持久JDO對象當應用程序提交事務時,其狀態將被保存的任何對象數據庫。持久JDO對象的變化將在數據庫中反映出來。
一個對象可以有兩種途徑變成為持久JDO對象: 它是由查詢產生的,(該查詢不是被設置為只讀方式)或者 否則它用create(java.lang.Object)或者update(java.lang.Object)方法來存入數據庫。
· 所有不是持久JDO對象的對象是瞬時JDO對象。
· 當應用程序提交事務或者回滾時,所有持久JDO對象自動變回得瞬時JDO對象。在Client應用中,我們可以使用begin(),commit()和rollback()管理事務。但是在J2EE應用中,依賴於容器的JDO對象或者是隱含地(基於Bean的事務屬性)或者是明確地使用javax.transaction.UserTransaction接口來管理事務。
如果一個持久JDO對象在處理期間被修改,在應用程序提交事務時相關的修改被存入數據庫。一旦事務回滾,將不會有任何相關的修改存入數據庫。
一旦事務處理完成,對象將再一次變回瞬時JDO對象。為了在兩種不同的事務處理中使用相同的對象,你必須再一次提交查詢來生成它。
一個JDO對象是屬於瞬時JDO對象還是持久JDO對象取決於當時的數據庫處理是處在那個事務處理中。在實際環境中,往往會發現一個JDO對象在一個的數據庫連接中屬於持久JDO對象,而在另一個數據庫連接將返回調用isPersistent(java.lang.Object)卻返回false(就是說它是瞬時JDO對象)。這使得可以讓一個JDO對象同時在2個數據庫連接屬於持久態:在一個數據庫連接查詢它在另一個數據庫連接創建它。
OQL查詢
OQL查詢通常用於在數據庫中查找並創建一個JDO對象. OQL查詢盒類似於SQL查詢,不過用對象名稱而不是 SQL名稱並且不支持join子句。例如,如果所裝載的對象是TestObject類,OQL查詢將從TestObject加載,而不管數據庫的實際的表名稱是test,test_object還是任何其它名稱。如果相關的對象需要join操作,Castor將自動地執行join操作。下列的代碼片斷使用OQL查詢來將所有對象裝載在一個給定的組中。注意,產品表和產品組表是相關的對象,JDBC查詢包括了join操作:
OQLQuery oql;
QueryResults results;
// Construct a new query and bind its parameters
// 建造一個新的查詢並且設定其參數
oql = db.getOQLQuery( "SELECT p FROM Product p WHERE Group=$1" );
oql.bind( groupId );
// 取回結果並且逐個打印
results = oql.execute();
while ( results.hasMore() ) {
System.out.println( results.next() );
}
下列的代碼片斷使用上面的查詢結果來獲得Product對象,把他們的價格減少25%,並且把修改的結果存儲到數據庫(本例采用Client應用模式):
while ( results.hasMore() ) {
Product prod;
prod = (Product) results.next(); //獲得Product對象
prod.markDown( 0.25 ); //價格減少25%
prod.setOnSale( true );
}
// 明確地提交事務處理
db.commit();
db.close();
如前面所說明的,查詢以三個步驟被執行。首先, 使用OQL語句從數據庫中創建查詢JDO對象。如果有參數,那麼第二個步驟包括設定這些paramaters。參數按相同的順序出現在OQL語句中。第三個步驟包括執行查詢和獲得 org.exolab.castor.jdo.QueryResults 類型的結果集合。
查詢能創建一次多次執行。每次它執行完成相關的參數自動消失,第二次查詢必須再次提供。當查詢第二次被執行時(有可能得到是不同的查詢結果),上次查詢的結果(舊的)還是能繼續使用。
查詢的一種特殊的形式提供了調用存儲過程的可能性。
如:
oql = db.getOQLQuery( "CALL sp_something($) AS myapp.Product" );
這裡sp_something是一個存儲過程返回一個或更多ResultSet對象,這些對象的順序和OQL查詢“SELECT p FROM myapp.Product p”的得到的順序是相同的。(對於一個沒有關聯的數據庫他的順序是首先按照唯一主鍵排列其次按照mapping.xml中指明的字段順序排列)
JDO對象的插入,刪除和修改
方法create(java.lang.Object)在數據庫中創建一個新的對象或者用JDO術語來說是:'持久化瞬時對象。' 以create方法創建的對象仍然是在提交數據庫事務中被存儲入數據庫。一旦提交數據庫回滾事務,那麼會在數據庫中上刪除這條紀錄。如果發生主鍵沖突,那麼將拋出(identity already exists in the database)異常。下列的代碼片斷創建一種新的 塑料轉椅對象(屬於產品類) 並把它關聯到以前查詢的得到的 furnitures (產品歸類) 對象中去:
Product prod;
// 創建一個塑料轉椅對象
prod = new Product();
prod.setSku( 5678 );
prod.setName( "塑料轉椅" );
prod.setPrice( 55.0 );
prod.setGroup( furnitures ); //加入到產品歸類的家具歸類實例中
// Make is persistent
db.create( prod ); // 存儲
remove方法(java.lang.Object)是和create方法相反的操作。是刪除一個持久的JDO對象。一旦提交數據庫事務處理,該對象就會在數據庫中被刪除, 同時在全部其他事務處理中消失。
不過,一旦提交回滾,那麼對象將仍然保留在數據庫中。
還有如果試圖刪除的紀錄不存在,那麼將拋出異常。
使用JDO和XML
XML在實際應用中越來越重要,可以說它的重要性僅次於DATABASE。結合執行Castor JDO和Castor XML能使得以XML作為輸入和輸出的形式來處理數據庫操作等等事務。這樣能夠大大提升你的軟件的應用范圍,同時並不需要付出很大的代價。
下列的代碼片斷結合持久JDO對象和瞬時DO對象來描述一種金融的操作。在這個例子中,我們要作的是:把金額從一個賬號轉賬到另外一個賬號中。這兩個賬號對象原來是存在數據庫中的。轉賬處理完畢後還必須把轉賬過程用XML文件表達出來。
在下面的實現中,我們將轉賬過程使用瞬時對象(即在數據庫中沒有記錄對應)描述,然後被用來產生描述轉賬過程的XML文件。通過調用一個額外程序(篇幅限制,這裡不作詳細解釋)用XSLT來把XML文件轉變成為HTML頁格式來描述轉賬過程。
Transfer tran;
Account from;
Account to;
OQLQuery oql;
tran = new Transfer();
// 建造查詢並且裝載兩個賬號
oql = db.getOQLQuery( "SELECT a FROM Account a WHERE Id=$" );
oql.bind( fromId );
from = oql.execute().nextElement();
oql.bind( toId );
to = oql.execute().nextElement();
// 轉賬處理
if ( from.getBalance() >= amount ) {
from.decBalance( amount );
to.incBalance( amount );
trans.setStatus( Transfer.COMPLETE );
trans.setAccount( from );
trans.setAmount( amount );
} else {
// 金額不夠,轉賬失敗
trans.setStatus( Transfer.OVERDRAFT );
}
// 生成XML描述
Marshaller.marshal( trans, outputStream );
運行上述代碼產生的XML看起來有可能象下面的樣子:
< ?xml version="1.0"?>
< report>
< status>Completed< /status>
< account id="1234-5678-90" balance="50" / >
< transfer amount="49.99" / >
< /report>