JDO(Java Data Object)是JCP中較早開發(fā)出來并形成規(guī)范的JSR12,該規(guī)范對數(shù)據(jù)的持久化存儲進(jìn)行了一系列規(guī)范,并已有眾多的商業(yè)產(chǎn)品和開源項(xiàng)目是基于該規(guī)范。作為一種需要引起重視的技術(shù),研究并探討其企業(yè)應(yīng)用可行性是十分重要的。
前言
在企業(yè)級的應(yīng)用開發(fā)中,常需要有良好的持久化技術(shù)來支持?jǐn)?shù)據(jù)存儲。通過良好的規(guī)范或API,將企業(yè)的領(lǐng)域業(yè)務(wù)對象進(jìn)行持久化存儲,大多采用O/R映射技術(shù)來進(jìn)行模式化的數(shù)據(jù)轉(zhuǎn)換及自動映射工作。
JDO(Java Data Object)是JCP中較早開發(fā)出來并形成規(guī)范的JSR12,該規(guī)范對數(shù)據(jù)的持久化存儲進(jìn)行了一系列規(guī)范,并已有眾多的商業(yè)產(chǎn)品和開源項(xiàng)目是基于該規(guī)范。作為一種需要引起重視的技術(shù),研究并探討其企業(yè)應(yīng)用可行性是十分重要的。
以下主要對JDO(JDO 1.0規(guī)范)的應(yīng)用開發(fā)技術(shù)作扼要介紹,通過該文,可以由淺入深、并較為全面地了解JDO,掌握主要的技術(shù)細(xì)節(jié)及過程,理解其運(yùn)行機(jī)制,并對企業(yè)級應(yīng)用有個總體的把握,這將有助于企業(yè)應(yīng)用軟件的技術(shù)選型、體系架構(gòu)及分析設(shè)計(jì)活動。
該文適合企業(yè)應(yīng)用架構(gòu)師、及關(guān)心數(shù)據(jù)持久層設(shè)計(jì)開發(fā)人員。
JDO基本思想及特點(diǎn)
企業(yè)信息系統(tǒng)的一個重要問題是解決數(shù)據(jù)的存儲,即持久化。在軟件開發(fā)過程中,分析員分析領(lǐng)域業(yè)務(wù),提取出領(lǐng)域業(yè)務(wù)模型,并對應(yīng)設(shè)計(jì)出數(shù)據(jù)庫中需要進(jìn)行存儲業(yè)務(wù)數(shù)據(jù)的數(shù)據(jù)庫表及相應(yīng)字段。
并根據(jù)業(yè)務(wù)流程,設(shè)計(jì)業(yè)務(wù)處理邏輯單元,進(jìn)行數(shù)據(jù)的加工、處理及存儲、查詢等業(yè)務(wù)。其中一個較為繁煩、枯燥的工作,就是處理大量的數(shù)據(jù)持久化代碼。
為了解決數(shù)據(jù)從業(yè)務(wù)對象層向數(shù)據(jù)存儲層之間的轉(zhuǎn)換工作,JDO提供了相應(yīng)的開發(fā)規(guī)范及API,解決了由Java對象直接存儲為數(shù)據(jù)庫相應(yīng)表的底層處理過程,有助于設(shè)計(jì)人員更加專注于面向業(yè)務(wù)流程、面向業(yè)務(wù)對象等較高層次的應(yīng)用。
由于采用JDO的映射機(jī)制,能降低了業(yè)務(wù)系統(tǒng)與數(shù)據(jù)存儲系統(tǒng)的耦合,使得業(yè)務(wù)系統(tǒng)相對于關(guān)系數(shù)據(jù)庫或?qū)ο笮蛿?shù)據(jù)庫,具有可移植性,同時,由于采用面向?qū)ο螅ǘ莻鹘y(tǒng)的面向記錄)的持久化技術(shù),系統(tǒng)更為輕便、簡潔,增強(qiáng)了可維護(hù)性。
JDO應(yīng)用示例及分析
以下將通過一些示例,由淺及深地講解JDO技術(shù)。
臨時對象與持久對象
這是一個普通的業(yè)務(wù)對象的代碼。
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;
}
} |
現(xiàn)在將它作為一個JDO中對象保存到數(shù)據(jù)庫中。代碼如下:
Book book = new Book();
book.setISBN(“isbn-1234567”);
book.setName(“Java設(shè)計(jì)模式”);
PersistenceManager
manager = persistenceManagerFactory.
getPersistenceManager();
manager.currentTransaction().begin();
manager.makePersistence(book);
manager.currentTransaction().commit(); |
Book類的實(shí)例book對JDO的API而言,就是一個持久對象。類Book是可持久類。那任何一個普通java類都是JDO的可持久類嗎?不是的。只有具備以下的條件,一個對象才可以被JDO持久到數(shù)據(jù)庫中。
它所屬類應(yīng)標(biāo)記為可持久的類,有以下兩種方法:
顯式:實(shí)現(xiàn)接口,javax.jdo.PersistenceCapable即可;
隱式:以Sun的JDO參考實(shí)現(xiàn)為例,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> |
并通過字節(jié)碼增強(qiáng)工具(本例采用Sun的字節(jié)碼增強(qiáng)工具)處理:
javac Book.java
java com.sun.jdori.enhancer.Main
Book.class Book.jdo |
通過上述兩種方法,獲得的Book.class才是一個可持久的類。
字節(jié)碼增強(qiáng)的有如下功能:當(dāng)應(yīng)用程序通過set方法修改某個字段1時,由于通過增強(qiáng)過程,在其內(nèi)部插入了某些代碼,JDO會獲得數(shù)據(jù)狀態(tài)變化的信息,從而在持久過程中,進(jìn)行有選擇性的處理。
按照J(rèn)DO規(guī)范,增強(qiáng)后的類可以在不同的JDO實(shí)現(xiàn)上使用,而無需重新編譯或增強(qiáng)。
并不是所有Book對象都是持久對象,只有當(dāng)makePersistence后,該對象才是持久對象,并會通過JDO實(shí)現(xiàn)存儲到數(shù)據(jù)庫中。通過JDO的供應(yīng)商擴(kuò)展標(biāo)記符(vendor-extension),可詳細(xì)描述Book類的存儲特性,如為該可持久類指定數(shù)據(jù)庫表和對應(yīng)字段。
持久對象查詢
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構(gòu)成更為強(qiáng)大的查詢。
使用Query查詢
Query可以指定過濾條件,是一種常用的查詢方式。
下例是查找條件為“書名以‘Java設(shè)計(jì)模式’開頭且出版日期小于今天”的書籍。
String filter =
“((String)name).startsWith(\”Java設(shè)計(jì)模式\”)
&& 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);
//傳入?yún)?shù)值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”方法中傳入該變量的實(shí)例。這種帶參數(shù)的查詢,很類似于我們以前采用JDBC的帶?的查詢方式。
其中startsWith(String s)是JDO提供的標(biāo)準(zhǔn)字符方法,類似的方法還有endsWith(String s)。
JDOQL:上述使用的就是一個JDOQL樣例,JDOQL是JDO規(guī)范一個組成部分。使用JDOQL可以使用應(yīng)用在不同的JDO實(shí)現(xiàn)上運(yùn)行。為了解決JDOQL的某些不足,JDO規(guī)范提供了支持特定JDO供應(yīng)商查詢語句接口。
查詢排序
下例是將查詢結(jié)果按“出版日期降序、書名升序”進(jìn)行排序。
Query query =
pm.newQuery(Book.class, filter);
String orderStr =
“publishDate decending, name ascending”;
query.setOrdering(orderStr);
results = query.execute(today); |
對象更新
當(dāng)客戶端對業(yè)務(wù)數(shù)據(jù)進(jìn)行了更新后,需要通過業(yè)務(wù)過程將其更新到持久層中。這有兩個過程,首先根據(jù)主鍵找到該實(shí)例,接著更新字段及提交。如下例,將指定書目編號的書本的出版日期進(jìn)行更改。
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來找到該對象呢?其實(shí)在本可持久類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采用程序標(biāo)識方式,即應(yīng)用程序傳入ID(字段isbn為“primary-key”)信息,JDO實(shí)現(xiàn)構(gòu)造出指定的“objectid-class”的實(shí)例(即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,并實(shí)現(xiàn) java.io.Serializable;
帶有一個公有且不帶參數(shù)的構(gòu)造方法;
當(dāng)字段作為主鍵時,須有公有的,且名稱和類型與持久類的字段一致,如:public String isbn;
equals 和 hashCode 須使用全部(特指多字段的聯(lián)合主鍵)的主鍵字段值;
類必須有一個構(gòu)造方法,與 toString 方法的處理過程是逆向過程;即將 toString 的輸出值,作為該構(gòu)造方法的輸入值,又可以重新生成該實(shí)例(如構(gòu)造方法“public BookKey(String oid)”)。
綜上所述,如果Book由兩個字段作為主鍵,如isbn和name,則可能的代碼是pm.newObjectIdInstance(Book.class,isbn+“#”+name),且BookKey的構(gòu)造方法作相應(yīng)更改,并有兩個公有字段“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實(shí)例
上述的所有操作與需要PersistenceManager實(shí)例,它可以在兩種環(huán)境方法下獲得:非受管環(huán)境和受管環(huán)境。
非受管環(huán)境
非受管環(huán)境是多指兩層開發(fā)模式,應(yīng)用程序直接獲得資源對象,進(jìn)行業(yè)務(wù)操作。一般事務(wù)管理、安全管理或資源管理都需要應(yīng)用程序自行維護(hù)。
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.getPersistence
ManagerFactory(properties);
PersistenceManager pm =
pmf.getPersistenceManager(); |
與JDBC獲取類似。
受管環(huán)境
受管環(huán)境一般是多層開發(fā)模式,尤其是處于J2EE應(yīng)用環(huán)境中,程序通過容器獲得資源對象,進(jìn)行業(yè)務(wù)操作,由于在容器環(huán)境下,事務(wù)、安全及資源管理都由容器進(jìn)行統(tǒng)一集中管理,從而簡化了代碼結(jié)構(gòu)。
以下是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環(huán)境下的JNDI上,有興趣可參考JCA相關(guān)的技術(shù)文檔。
事務(wù)管理
事務(wù)管理及使用,主要有以下三種情形。
使用JDO事務(wù)的Bean管理情形
一般在非J2EE環(huán)境下,使用該事務(wù)管理模式。
PersistenceManager pm =
pmf.getPersistenceManager();
pm.currentTransaction().begin();
//do some business with jdo
pm.currentTransaction().commit();
pm.close(); |
使用JTA事務(wù)的Bean管理情形
一般在J2EE環(huán)境下,采用Bean管理的事務(wù)情形下,使用以下方式。
該方式可用在EJB的事務(wù)環(huán)境下。
xxxContext.getUser
Transaction().begin();
PersistenceManager pm =
pmf.getPersistenceManager();
//do some business with jdo
xxxContext.getUserTransaction().commit();
pm.close(); |
使用JTA事務(wù)的容器管理情形
一般在J2EE環(huán)境下,采用容器管理的事務(wù)情形下,使用如下方式。
如下是某個會話Bean的業(yè)務(wù)方法。
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);
}
}
} |
綜上,可以得出結(jié)論,JDO的操作完全與JDBC的操作相差無幾。
JDO高級應(yīng)用
復(fù)雜對象的持久化
在實(shí)際的應(yīng)用中,一個可持久化類要遠(yuǎn)比Book類復(fù)雜很多。它可能會引用其它的Java類型、類、集合或數(shù)組,及可能復(fù)雜的繼承關(guān)系。當(dāng)這些對象的狀態(tài)發(fā)生變化時,JDO是如何感知及跟蹤狀態(tài)變化?
JDO提供了相應(yīng)的API及規(guī)范來實(shí)現(xiàn)該功能。
基本類型及引用
可持久化類的字段能被JDO實(shí)現(xiàn)進(jìn)行持久化的原則。原始類型、java.util.Date等被支持(其它較為復(fù)雜或可選特性,詳見JDO規(guī)范);如果引用是一個可持久類,則JDO進(jìn)行持久化處理;通過元數(shù)據(jù)(如jdo文件)聲明的字段,一般是非可持久化類的引用,JDO進(jìn)行持久化;
前面兩種情形,當(dāng)狀態(tài)發(fā)生變化時,JDO會自動感知,但如果引用是非可持久化類,則需要代碼進(jìn)行顯式通知,否則JDO不會將變化進(jìn)行存儲。如下例:
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方法來更新數(shù)據(jù)。JDO的“makeDirty”方法,主要負(fù)責(zé)通知JDO實(shí)現(xiàn),可持久化類Book某個實(shí)例(this)的“picture”字段發(fā)生了變化。
集合
可持久類的字段引用為集合時,按照J(rèn)DO規(guī)范,強(qiáng)制支持java.util.HashSet,對HashMap、HashTable、TreeMap、TreeSet、LinkedList、ArrayList及Vector的支持對JDO實(shí)現(xiàn)而言是可選的,通過PersistenceManagerFactory的supportedOptions方法獲得實(shí)現(xiàn)特性。
數(shù)組
如果可持久類的引用是數(shù)組類型,當(dāng)數(shù)組單元發(fā)生變化時,需要調(diào)用“makeDirty”來通知JDO實(shí)現(xiàn),該實(shí)例的數(shù)組引用內(nèi)容發(fā)生了變化。與引用是非可持久類實(shí)例不同,不需要進(jìn)行JDO的元數(shù)據(jù)聲明。
繼承
如果使用可持久性,一般繼承的子類與父類都為可持久類,以減少系統(tǒng)復(fù)雜性,這時需要在子類的元數(shù)據(jù)中指出其可持久超類,如下:
<
class name=“TechBook”
persistence-capable-superclass=“Book”/> |
可為非持久類擴(kuò)展持久類,或可為持久類擴(kuò)展非可持久類;這兩種情形JDO實(shí)現(xiàn)都將忽略非
可持久類的字段部分,而不保存到數(shù)據(jù)庫。
JDO的一些不足之處
JDO對數(shù)據(jù)的持久化技術(shù)相比于成熟的SQL,還有不足之處,這些不足在某些情況下將可能會影響數(shù)據(jù)處理部分的設(shè)計(jì)實(shí)現(xiàn)。以下列舉了常用數(shù)據(jù)訪問的必要功能
查詢增強(qiáng)
如字符串不支持通配符、大小寫比較;
不支持聚合操作,無法實(shí)現(xiàn)MIN、MAX、SUM和AVG;
不支持投影操作,JDO查詢返回為對象,而不像SQL那樣返回字段;
不支持聯(lián)合、交叉(UNION/INTERSECT);
不支持JOIN、IN和EXISTS;
企業(yè)應(yīng)用探究
由于JDO采用面向?qū)ο蟮某志没幚砑夹g(shù),從而為解決企業(yè)業(yè)務(wù)系統(tǒng)的持久化問題提供了一個新技術(shù)解決方案。但是先進(jìn)的未必就最適用。在某些應(yīng)用場景下,需要結(jié)合各種因素,采取靈活的策略。
面向?qū)ο笈c面向記錄
現(xiàn)在大多業(yè)務(wù)系統(tǒng)都采用面向?qū)ο蠓治鲈O(shè)計(jì),這就需要每個應(yīng)用系統(tǒng)都自行實(shí)現(xiàn)將對象映射成記錄,并存儲到數(shù)據(jù)庫中,而有JDO這樣的面向?qū)ο蟮某志没夹g(shù),在某種程度上解放了這種轉(zhuǎn)化設(shè)計(jì)的不規(guī)范性,進(jìn)而獲得相對更優(yōu)的系統(tǒng)結(jié)構(gòu)。
另一方面,一個業(yè)務(wù)系統(tǒng)的數(shù)據(jù)持久化,一般都有這樣的過程:對象層->記錄層->物理層,JDO無疑使分析設(shè)計(jì)人員從記錄層的苦海中解脫出來,從而更加專注于對象層,這對開發(fā)人員無疑是一個令人歡欣鼓舞的技術(shù)。
JDO并不能完全代替JDBC。
根據(jù)經(jīng)典的“8-2原理”,如果用JDO來解決80%的問題,余下的20%由JDBC來實(shí)現(xiàn),這種相互補(bǔ)充,各取所長的策略,是一個很有效的辦法。
這樣一方面可以獲得較好的結(jié)構(gòu)及提升開發(fā)質(zhì)量,另一方面解決了JDO的某些技術(shù)不足,并可根據(jù)以后的技術(shù)變化,再做適當(dāng)轉(zhuǎn)化。
性能問題
JDO與JDBC究竟誰的性能更優(yōu),目前還沒有一個權(quán)威、且令人滿意的答案。但對于一些JDO實(shí)現(xiàn)而言,通過采用緩存機(jī)制,使得性能有了較大提高。
跨數(shù)據(jù)庫
使用JDO的系統(tǒng)能更好地進(jìn)行數(shù)據(jù)庫移植,甚至可以在對象數(shù)據(jù)庫上運(yùn)行;當(dāng)然JDBC處理層如果完全遵循SQL-92標(biāo)準(zhǔn),也同樣具有很好的跨數(shù)據(jù)庫能力。
posted on 2005-11-04 13:53
Sung 閱讀(252)
評論(0) 編輯 收藏 所屬分類:
Java