jdo(java data object)是jcp中較早開發出來并形成規范的jsr12,該規范對數據的持久化存儲進行了一系列規范,并已有眾多的商業產品和開源項目是基于該規范。作為一種需要引起重視的技術,研究并探討其企業應用可行性是十分重要的。
  前言 
  在企業級的應用開發中,常需要有良好的持久化技術來支持數據存儲。通過良好的規范或api,將企業的領域業務對象進行持久化存儲,大多采用o/r映射技術來進行模式化的數據轉換及自動映射工作。
  jdo(java data object)是jcp中較早開發出來并形成規范的jsr12,該規范對數據的持久化存儲進行了一系列規范,并已有眾多的商業產品和開源項目是基于該規范。作為一種需要引起重視的技術,研究并探討其企業應用可行性是十分重要的。
  以下主要對jdo(jdo 1.0規范)的應用開發技術作扼要介紹,通過該文,可以由淺入深、并較為全面地了解jdo,掌握主要的技術細節及過程,理解其運行機制,并對企業級應用有個總體的把握,這將有助于企業應用軟件的技術選型、體系架構及分析設計活動。
  該文適合企業應用架構師、及關心數據持久層設計開發人員。
  jdo基本思想及特點
  企業信息系統的一個重要問題是解決數據的存儲,即持久化。在軟件開發過程中,分析員分析領域業務,提取出領域業務模型,并對應設計出數據庫中需要進行存儲業務數據的數據庫表及相應字段。
  并根據業務流程,設計業務處理邏輯單元,進行數據的加工、處理及存儲、查詢等業務。其中一個較為繁煩、枯燥的工作,就是處理大量的數據持久化代碼。為了解決數據從業務對象層向數據存儲層之間的轉換工作,jdo提供了相應的開發規范及api,解決了由java對象直接存儲為數據庫相應表的底層處理過程,有助于設計人員更加專注于面向業務流程、面向業務對象等較高層次的應用。
  由于采用jdo的映射機制,能降低了業務系統與數據存儲系統的耦合,使得業務系統相對于關系數據庫或對象型數據庫,具有可移植性,同時,由于采用面向對象(而非傳統的面向記錄)的持久化技術,系統更為輕便、簡潔,增強了可維護性。 
  jdo應用示例及分析
  以下將通過一些示例,由淺及深地講解jdo技術。
  臨時對象與持久對象
  這是一個普通的業務對象的代碼。
package business.model; 
public class book { 
 private string isbn; 
 private string name; 
 private date publishdate; 
 public void setisbn(string isbn){ 
  this.isbn = isbn; 
 } 
 public string getisbn(){ 
  return this.isbn; 
 } 
 public void setname(string name){ 
  this.name = name; 
 } 
 public string getname(){ 
  return this.name; 
 } 
 public void setpublishdate(date pubdate){ 
  this.publishdate = pubdate; 
 } 
 public date getpublishdate(){ 
  return this.publishdate; 
 } 
} 
  現在將它作為一個jdo中對象保存到數據庫中。代碼如下:
book book = new book(); 
book.setisbn(“isbn-1234567”); 
book.setname(“java設計模式”); 
persistencemanager manager = persistencemanagerfactory.getpersistencemanager(); 
manager.currenttransaction().begin(); 
manager.makepersistence(book); 
manager.currenttransaction().commit(); 
  book類的實例book對jdo的api而言,就是一個持久對象。類book是可持久類。那任何一個普通java類都是jdo的可持久類嗎?不是的。只有具備以下的條件,一個對象才可以被jdo持久到數據庫中。
  它所屬類應標記為可持久的類,有以下兩種方法: 
  顯式:實現接口,javax.jdo.persistencecapable即可;
  隱式:以sun的jdo參考實現為例,book.java類的相同路徑下還須有book.jdo文件。
<?xml version=“1.0” encoding = “utf-8”?> 
<!doctype jdo system “jdo.dtd”> 
<jdo> 
<package name = “business.model”> 
<class name = “book”/> 
</package> 
</jdo> 
  并通過字節碼增強工具(本例采用sun的字節碼增強工具)處理,
javac book.java 
java com.sun.jdori.enhancer.main book.class book.jdo。 
  通過上述兩種方法,獲得的book.class才是一個可持久的類。
  字節碼增強的有如下功能:當應用程序通過set方法修改某個字段1時,由于通過增強過程,在其內部插入了某些代碼,jdo會獲得數據狀態變化的信息,從而在持久過程中,進行有選擇性的處理。
  按照jdo規范,增強后的類可以在不同的jdo實現上使用,而無需重新編譯或增強。
  并不是所有book對象都是持久對象,只有當makepersistence后,該對象才是持久對象,并會通過jdo實現存儲到數據庫中。通過jdo的供應商擴展標記符(vendor-extension),可詳細描述book類的存儲特性,如為該可持久類指定數據庫表和對應字段。
  持久對象查詢
  jdo查詢主要有以下兩種方式。
  ·使用extend查詢
  extend可以查詢指定類及子類的持久對象。
persistencemanager manager = persistencemanagerfactory.getpersistencemanager(); 
manager.currenttransaction().begin(); 
extend extend = manager.getextend(book.class,true);//true表明同時查詢子類 
iterator it = extend.iterator(); 
while(it.hasnext()){ 
 book book = (book)it.next(); 
 system.out.println(book.getisbn()); 
} 
extend.closeall(); 
manager.currenttransaction().commit(); 
  extend查詢方法,提供了一種基于類的查詢途徑,它可以與下面的query構成更為強大的查詢。
  ·使用query查詢
  query可以指定過濾條件,是一種常用的查詢方式。
  下例是查找條件為“書名以‘java設計模式’開頭且出版日期小于今天”的書籍。
string filter = “((string)name).startswith(/”java設計模式/”) && publishdate < today”; 
query query = pm.getquery(book.class,filter); 
query.declareimports(“import java.util.date”); 
query.declareparameters(“date today); 
date today = new date(); 
results = (collection)query.execute(today);//傳入參數值today 
if (results.isempty()){ 
 system.out.println(“no data!”); 
}else{ 
 iterator it = results.iterator(); 
 while(it.hasnext()){ 
  book book = (book)it.next(); 
  system.out.println(“book name:” + book.getname() + “, isbn:” + book.getisbn()); 
 } 
} 
  注:該條件使用了一個變元‘today’,通過“declareparameters”來聲明該變量,并在“execute”方法中傳入該變量的實例。
  這種帶參數的查詢,很類似于我們以前采用jdbc的帶?的查詢方式。
  其中startswith(string s)是jdo提供的標準字符方法,類似的方法還有endswith(string s)。
  jdoql:上述使用的就是一個jdoql樣例,jdoql是jdo規范一個組成部分。使用jdoql可以使用應用在不同的jdo實現上運行。為了解決jdoql的某些不足,jdo規范提供了支持特定jdo供應商查詢語句接口。
  ·查詢排序
  下例是將查詢結果按“出版日期降序、書名升序”進行排序。
query query = pm.newquery(book.class, filter); 
string orderstr = “publishdate decending, name ascending”; 
query.setordering(orderstr); 
results = query.execute(today); 
  對象更新
  當客戶端對業務數據進行了更新后,需要通過業務過程將其更新到持久層中。
  這有兩個過程,首先根據主鍵找到該實例,接著更新字段及提交。 如下例,將指定書目編號的書本的出版日期進行更改。
public void updatebookpublishdate(string isbn, date newdate){ 
persistencemanager pm = null; 
try{ 
 pm = pmf.getpersistencemanager(); 
 object obj = pm.newobjectidinstance(book.class,isbn); 
 book book = (book)pm.getobjectbyid(obj,true); 
 book.setpublishdate(newdate); 
}catch(exception e){ 
 xxxcontext.setrollbackonly(); 
 throw new exception(e); 
}finally{ 
 try{ 
  if (pm != null && !pm.isclosed()){ 
   pm.close(); 
  } 
 }catch(exception ex){ 
  system.out.println(ex); 
 } 
} 
  注,在persistencemanager使用newobjectidinstance()方法時,jdo是如何知道通過書目編號isbn來找到該對象呢?
  其實在本可持久類book的jdo描述文件中,還需提供如下信息:
<?xml version=“1.0” encoding = “utf-8”?> 
<!doctype jdo system “jdo.dtd”> 
<jdo> 
<package name = “business.model”> 
<class name = “book” identity-type=“application” objectid-class=“bookkey” > 
<field name=“isbn” primary-key=“true”/> 
</class> 
</package> 
</jdo> 
  其中“identity-type=“application””聲明可持久類book采用程序標識方式,即應用程序傳入id(字段isbn為“primary-key”)信息,jdo實現構造出指定的“objectid-class”的實例(即newobjectidinstance過程),并由jdo來檢索出指定的持久化對象(即getobjectbyid)。
  bookkey類源碼如下:
package businesss.model; 
public class bookkey implements java.io.serializable{ 
 public string isbn; 
 public bookkey(){} 
 public bookkey(string oid){ 
  isbn = oid; 
 } 
 public string tostring(){ 
  return isbn; 
 } 
 public boolean equals(object obj){ 
  return isbn.equals((bookkey)obj).isbn); 
 } 
 public int hashcode(){ 
  return isbn.hashcode(); 
 } 
} 
  符合 jdo 的“objectid-class”類,如“bookkey”,須具備以下條件:
  類聲明為 public,并實現 java.io.serializable;
  帶有一個公有且不帶參數的構造方法;
  當字段作為主鍵時,須有公有的,且名稱和類型與持久類的字段一致,如:public string isbn; 
  equals 和 hashcode 須使用全部(特指多字段的聯合主鍵)的主鍵字段值; 
  類必須有一個構造方法,與 tostring 方法的處理過程是逆向過程;即將 tostring 的輸出值,作為該構造方法的輸入值,又可以重新生成該實例(如構造方法“public bookkey(string oid)”)。
  綜上所述,如果book由兩個字段作為主鍵,如isbn和name,則可能的代碼是pm.newobjectidinstance(book.class,isbn+“#”+name),且bookkey的構造方法作相應更改,并有兩個公有字段“isbn”和“name”。  對象刪除
  對象刪除采用方法deletepersistence。示例如下:
pm.currenttransaction().begin(); 
object obj = pm.newobjectidinstance(book.class,isbn); 
book book = (book)pm.getobjectbyid(obj,true); 
pm.deletepersistence(book); 
pm.currenttransaction().commit(); 
  獲得persistencemanager實例
  上述的所有操作與需要persistencemanager實例,它可以在兩種環境方法下獲得:非受管環境和受管環境。
  ·非受管環境
  非受管環境是多指兩層開發模式,應用程序直接獲得資源對象,進行業務操作。一般事務管理、安全管理或資源管理都需要應用程序自行維護。
properties properties = new properties(); 
properties.put(“javax.jdo.persistencemanagerfactoryclass”, “com.xxx.jdo.xxxpmfclass”); 
properties.put(“javax.jdo.option.connectionurl”, “xxx”); 
properties.put(“javax.jdo.option.connectionusername”, “xxx”); 
properties.put(“javax.jdo.option.connectionpassword”, “xxx”); 
persistencemanagerfactory pmf = jdohelper.getpersistencemanagerfactory(properties); 
persistencemanager pm = pmf.getpersistencemanager(); 
  與jdbc獲取類似。
  ·受管環境
  受管環境一般是多層開發模式,尤其是處于j2ee應用環境中,程序通過容器獲得資源對象,進行業務操作,由于在容器環境下,事務、安全及資源管理都由容器進行統一集中管理,從而簡化了代碼結構。
  以下是ejb(entitybean、sessionbean、messagedrivenbean)中的setxxxcontext中的代碼示例。
private persistencemanagerfactory pmf; 
public void setxxxcontext(xxxcontext context){ 
 try{ 
  initialcontext ctx = new initialcontext(); 
  object obj = ctx.lookup(“java:compenvjdofactory”); 
  pmf = (persistencemanagerfactory)portableremoteobject.narrow(o,persistencemanagerfactory.class); 
 }catch(namingexception e){ 
  throw new exception(e); 
 } 
} 
  pmf是如何綁定到j2ee環境下的jndi上,有興趣可參考jca相關的技術文檔。
  事務管理
  事務管理及使用,主要有以下三種情形。
  使用jdo事務的bean管理情形
  一般在非j2ee環境下,使用該事務管理模式。
persistencemanager pm = pmf.getpersistencemanager(); 
pm.currenttransaction().begin(); 
//do some business with jdo 
pm.currenttransaction().commit(); 
pm.close(); 
  使用jta事務的bean管理情形
  一般在j2ee環境下,采用bean管理的事務情形下,使用以下方式。
  該方式可用在ejb的事務環境下。
xxxcontext.getusertransaction().begin(); 
persistencemanager pm = pmf.getpersistencemanager(); 
//do some business with jdo 
xxxcontext.getusertransaction().commit(); 
pm.close(); 
  ·使用jta事務的容器管理情形
  一般在j2ee環境下,采用容器管理的事務情形下,使用如下方式。
  如下是某個會話bean的業務方法。
public void dobusiness(){ 
 persistencemanager pm ; 
 try{ 
  pm = pmf.getpersistencemanager(); 
  //do some business with jdo 
 }catch(exception e){ 
  xxxcontext.setrollbackonly(); 
  system.out.println(e); 
 }finally{ 
  try{ 
   if (pm != null && !pm.isclosed()) 
    pm.close(); 
  }catch(exception ex){ 
   system.out.println(ex); 
  } 
 } 
} 
  綜上,可以得出結論,jdo的操作完全與jdbc的操作相差無幾。  jdo高級應用
  ·復雜對象的持久化
  在實際的應用中,一個可持久化類要遠比book類復雜很多。它可能會引用其它的java類型、類、集合或數組,及可能復雜的繼承關系。
  當這些對象的狀態發生變化時,jdo是如何感知及跟蹤狀態變化?
  jdo提供了相應的api及規范來實現該功能。
  ·基本類型及引用
  可持久化類的字段能被jdo實現進行持久化的原則。
  原始類型、java.util.date等被支持(其它較為復雜或可選特性,詳見jdo規范); 
  如果引用是一個可持久類,則jdo進行持久化處理; 
  通過元數據(如jdo文件)聲明的字段,一般是非可持久化類的引用,jdo進行持久化;
  前面兩種情形,當狀態發生變化時,jdo會自動感知,但如果引用是非可持久化類,則需要代碼進行顯式通知,否則jdo不會將變化進行存儲。如下例:
public class book { 
 …… 
 private object picture; 
 public void setpicture(object pic){ 
  picture = pic; 
 } 
 public object getpicture(){ 
  return picture; 
 } 
}
  該類字段picture需要持久化,但java.lang.object不是一個可持久類,故聲明如下:
<?xml version=“1.0” encoding = “utf-8”?> 
<!doctype jdo system “jdo.dtd”> 
<jdo> 
<package name = “business.model”> 
<class name = “book” > 
<field name=“picture” persistence-modifier=“persistent”/> 
</class> 
</package> 
</jdo> 
  如果其它模塊通過getpicture獲得對象,并在jdo不可感知的外部,修改對象,則變化不會存儲,較好的辦法是修改setpicture方法,如下:
public void setpicture(object pic){ 
 jdohelper.makedirty(this, “picture”); 
 picture = pic; 
} 
  并通過setpicture方法來更新數據。
  jdo的“makedirty”方法,主要負責通知jdo實現,可持久化類book某個實例(this)的“picture”字段發生了變化。
  ·集合
  可持久類的字段引用為集合時,按照jdo規范,強制支持java.util.hashset,對hashmap、hashtable、treemap、treeset、linkedlist、arraylist及vector的支持對jdo實現而言是可選的,通過persistencemanagerfactory的supportedoptions方法獲得實現特性。
  ·數組
  如果可持久類的引用是數組類型,當數組單元發生變化時,需要調用“makedirty”來通知jdo實現,該實例的數組引用內容發生了變化。
  與引用是非可持久類實例不同,不需要進行jdo的元數據聲明。
  ·繼承
  如果使用可持久性,一般繼承的子類與父類都為可持久類,以減少系統復雜性,這時需要在子類的元數據中指出其可持久超類,如下:
<class name=“techbook” persistence-capable-superclass=“book”/> 
  可為非持久類擴展持久類,或可為持久類擴展非可持久類;這兩種情形jdo實現都將忽略非可持久類的字段部分,而不保存到數據庫。
  jdo的一些不足之處
  jdo對數據的持久化技術相比于成熟的sql,還有不足之處,這些不足在某些情況下將可能會影響數據處理部分的設計實現。
  以下列舉了常用數據訪問的必要功能
  ·查詢增強
  如字符串不支持通配符、大小寫比較;
  不支持聚合操作,無法實現min、max、sum和avg;
  不支持投影操作,jdo查詢返回為對象,而不像sql那樣返回字段;
  不支持聯合、交叉(union/intersect);
  不支持join、in和exists;
  企業應用探究
  由于jdo采用面向對象的持久化處理技術,從而為解決企業業務系統的持久化問題提供了一個新技術解決方案。但是先進的未必就最適用。在某些應用場景下,需要結合各種因素,采取靈活的策略。
  ·面向對象與面向記錄
  現在大多業務系統都采用面向對象分析設計,這就需要每個應用系統都自行實現將對象映射成記錄,并存儲到數據庫中,而有jdo這樣的面向對象的持久化技術,在某種程度上解放了這種轉化設計的不規范性,進而獲得相對更優的系統結構;另一方面,一個業務系統的數據持久化,一般都有這樣的過程:對象層->記錄層->物理層,jdo無疑使分析設計人員從記錄層的苦海中解脫出來,從而更加專注于對象層,這對開發人員無疑是一個令人歡欣鼓舞的技術。
  ·jdo并不能完全代替jdbc。
  根據經典的“8-2原理”,如果用jdo來解決80%的問題,余下的20%由jdbc來實現,這種相互補充,各取所長的策略,是一個很有效的辦法。
  這樣一方面可以獲得較好的結構及提升開發質量,另一方面解決了jdo的某些技術不足,并可根據以后的技術變化,再做適當轉化。
  ·性能問題
  jdo與jdbc究竟誰的性能更優,目前還沒有一個權威、且令人滿意的答案。但對于一些jdo實現而言,通過采用緩存機制,使得性能有了較大提高。
  ·跨數據庫
  使用jdo的系統能更好地進行數據庫移植,甚至可以在對象數據庫上運行;當然jdbc處理層如果完全遵循sql-92標準,也同樣具有很好的跨數據庫能力。