在企業級的應用開發中,常需要有良好的持久化技術來支持數據存儲。通過良好的規範或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實現而言,通過採用緩存機制,使得性能有了較大提高。
|