明輝手游網(wǎng)中心:是一個(gè)免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺(tái)!

Java數(shù)據(jù)對(duì)象技術(shù)JDO

[摘要]作為異軍突起的新型語言,Java定義了一個(gè)標(biāo)準(zhǔn)的運(yùn)行環(huán)境,用戶定義的類在其中得到執(zhí)行。這些用戶自定義類的實(shí)例代表了真實(shí)環(huán)境中的數(shù)據(jù),包括儲(chǔ)存在數(shù)據(jù)庫、文件或某些大型事務(wù)處理系統(tǒng)中的數(shù)據(jù),而小型系統(tǒng)通常也需要一種在本地負(fù)責(zé)控制數(shù)據(jù)存儲(chǔ)的機(jī)制。    由于數(shù)據(jù)訪問技術(shù)在不同的數(shù)據(jù)源類型中是不一樣的,因...

  作為異軍突起的新型語言,Java定義了一個(gè)標(biāo)準(zhǔn)的運(yùn)行環(huán)境,用戶定義的類在其中得到執(zhí)行。這些用戶自定義類的實(shí)例代表了真實(shí)環(huán)境中的數(shù)據(jù),包括儲(chǔ)存在數(shù)據(jù)庫、文件或某些大型事務(wù)處理系統(tǒng)中的數(shù)據(jù),而小型系統(tǒng)通常也需要一種在本地負(fù)責(zé)控制數(shù)據(jù)存儲(chǔ)的機(jī)制。

   由于數(shù)據(jù)訪問技術(shù)在不同的數(shù)據(jù)源類型中是不一樣的,因此對(duì)數(shù)據(jù)進(jìn)行訪問成了給程序開發(fā)人員的一種挑戰(zhàn),程序員需要對(duì)每一種類型的數(shù)據(jù)源使用特定的編程接口(API),即必須至少知道兩種語言來基于這些數(shù)據(jù)源開發(fā)業(yè)務(wù)應(yīng)用:Java語言和由數(shù)據(jù)源所決定的數(shù)據(jù)訪問語言。這種數(shù)據(jù)訪問語言一般根據(jù)數(shù)據(jù)源的不同而不同,這使得學(xué)習(xí)使用某種數(shù)據(jù)源的開發(fā)成本相應(yīng)提升。

  在Java數(shù)據(jù)對(duì)象技術(shù)(JDO)發(fā)布之前,通常有三種方式用于存儲(chǔ)Java數(shù)據(jù):串行化(即Serialization,也稱序列化)、JDBC和EJB中的CMP(容控存儲(chǔ))方式。串行化用于將某個(gè)對(duì)象的狀態(tài),以及它所指向的其它對(duì)象結(jié)構(gòu)圖全部寫到一個(gè)輸出流中(比如文件、網(wǎng)絡(luò)等等),它保證了被寫入的對(duì)象之間的關(guān)系,這樣一來,在另一時(shí)刻,這個(gè)對(duì)象結(jié)構(gòu)圖可以完整地重新構(gòu)造出來。但串行化不支持事務(wù)處理、查詢或者向不同的用戶共享數(shù)據(jù)。它只允許在最初串行化時(shí)的粒度(指訪問對(duì)象的接口精細(xì)程度)基礎(chǔ)上進(jìn)行訪問,并且當(dāng)應(yīng)用中需要處理多種或多次串行化時(shí)很難維護(hù)。串行化只適用于最簡單的應(yīng)用,或者在某些無法有效地支持?jǐn)?shù)據(jù)庫的嵌入式系統(tǒng)中。

  JDBC要求你明確地處理數(shù)據(jù)字段,并且將它們映射到關(guān)系數(shù)據(jù)庫的表中。開發(fā)人員被迫與兩種區(qū)別非常大的數(shù)據(jù)模型、語言和數(shù)據(jù)訪問手段打交道:Java,以及SQL中的關(guān)系數(shù)據(jù)模型。在開發(fā)中實(shí)現(xiàn)從關(guān)系數(shù)據(jù)模型到Java對(duì)象模型的映射是如此的復(fù)雜,以致于多數(shù)開發(fā)人員從不為數(shù)據(jù)定義對(duì)象模型;他們只是簡單地編寫過程化的Java代碼來對(duì)底層的關(guān)系數(shù)據(jù)庫中的數(shù)據(jù)表進(jìn)行操縱。最終結(jié)果是:他們根本不能從面向?qū)ο蟮拈_發(fā)中得到任何好處。

  EJB組件體系是被設(shè)計(jì)為支持分布式對(duì)象計(jì)算的。它也包括對(duì)容器管理持續(xù)性Container Managed Persistence(參見術(shù)語表)的支持來實(shí)現(xiàn)持續(xù)性。主要由于它們的分布式特性,EJB應(yīng)用比起JDO來復(fù)雜得多,對(duì)資源的消耗也大得多。不過,JDO被設(shè)計(jì)成具有一定的靈活性,這樣一來,JDO產(chǎn)品都可以用來在底層實(shí)現(xiàn)EJB的存儲(chǔ)處理,從而與EJB容器結(jié)合起來。如果你的應(yīng)用需要對(duì)象存儲(chǔ),但不需要分布式的特性,你可以使用JDO來代替EJB組件。在EJB環(huán)境中最典型的JDO使用方案就是讓EJB中的對(duì)話組件(Session Bean)直接訪問JDO對(duì)象,避免使用實(shí)體組件(Entity Bean)。EJB組件必須運(yùn)行在一個(gè)受控(Managed,參見術(shù)語表)的應(yīng)用服務(wù)環(huán)境。但JDO應(yīng)用可以運(yùn)行在受控環(huán)境中,也可以運(yùn)行在不受控的獨(dú)立環(huán)境中,這些使你可以靈活地選擇最合適的應(yīng)用運(yùn)行環(huán)境。

   如果你將精力集中在設(shè)計(jì)Java對(duì)象模型上,然后用JDO來進(jìn)行存儲(chǔ)你的數(shù)據(jù)類的實(shí)例,你將大大提高生產(chǎn)力和開發(fā)效率。你只需要處理一種信息模型。而JDBC則要求你理解關(guān)系模型和SQL語言(譯者注:JDO并不是要取代JDBC,而是建立在JDBC基礎(chǔ)上的一個(gè)抽象的中間層,提供更簡單的數(shù)據(jù)存儲(chǔ)接口)。即使是在使用EJB CMP(即容控存儲(chǔ),參見術(shù)語表)的時(shí)候,你也不得不學(xué)習(xí)與EJB體系相關(guān)的許多其它方面的內(nèi)容,并且在建模方面還有一些JDO中不存在的局限性。
  JDO規(guī)范了JDO運(yùn)行環(huán)境和你的可存儲(chǔ)對(duì)象類之間的約定。JDO被設(shè)計(jì)成支持多種數(shù)據(jù)源,包括一般情況下考慮不到的數(shù)據(jù)庫之類的數(shù)據(jù)源。從現(xiàn)在開始,我們使用數(shù)據(jù)庫(參見術(shù)語表)這一概念來表示任何你通過JDO來訪問的底層數(shù)據(jù)源。

   本章將會(huì)展開討論JDO的基本能力,這些基于對(duì)一個(gè)虛擬的Media Mania公司所開發(fā)的一個(gè)小型應(yīng)用進(jìn)行細(xì)致的分析。這個(gè)公司在遍布美國的很多商店中出租和出售多種形式的娛樂音像產(chǎn)品。他們的商店中有一些售貨亭,提供一些電影以及電影中的演員的信息。這些信息對(duì)客戶和商店的職員開放,以幫助選擇適合客戶口味的商品。

  定義數(shù)據(jù)對(duì)象模型

  我們將建立一個(gè)UML類圖,顯示一個(gè)公司的對(duì)象模型的相關(guān)類以及相互之間的關(guān)系。一個(gè)Movie(電影)對(duì)象表示一部特定的電影。每個(gè)至少在一部電影中出演角色的演員由一個(gè)Actor(演員)對(duì)象代表。而Role(角色)類表示某個(gè)演員在某部電影中扮演的特定角色,因此Role類也表示了電影和演員之間的一種關(guān)系,這種關(guān)系包含一個(gè)屬性(電影中的角色名)。每部電影包含一到多個(gè)角色。每個(gè)演員可以在不同的電影中扮演不同的角色,甚至在同一部電影中扮演多個(gè)角色。

  我們會(huì)將這些數(shù)據(jù)類以及操縱這些數(shù)據(jù)類實(shí)例的的程序放到com.mecdiamania.prototype包中。

  需要存儲(chǔ)的類

  我們定義Movie、Actor和Role這幾個(gè)類為可持續(xù)的,表示它們的實(shí)例是可以被儲(chǔ)存到數(shù)據(jù)庫中的。首先我們看看每個(gè)類的完整的源代碼。每個(gè)類中有一個(gè)package語句,因此可以很清楚地看到本例用到的每個(gè)類分別在哪個(gè)包中。

  例1-1顯示了Movie類的源代碼。JDO是定義在javax.jdo包中的,注意這個(gè)類并不一定要導(dǎo)入任何具體的JDO類。Java中的引用和java.util包中的Collection及相關(guān)子類(接口)被用來表示我們的類之間的關(guān)系,這是大多數(shù)Java應(yīng)用中的標(biāo)準(zhǔn)方式。

  Movie類中的屬性使用Java中的標(biāo)準(zhǔn)類型,如String、Date、int等等。你可以將屬性聲明為private的,不需要對(duì)每一屬性定義相應(yīng)的get和set方法。Movie類中還有一些用于訪問這些私有屬性的方法,盡管這些方法在程序中的其它部分會(huì)用到,但它們并不是JDO所要求的。你可以使用屬性包裝來提供僅僅是抽象建模所需要的方法。這個(gè)類還有一些靜態(tài)屬性(static的),這些屬性并不存儲(chǔ)到數(shù)據(jù)庫。

   "genres"屬性是一個(gè)String型的,內(nèi)容是該電影所屬的電影風(fēng)格(動(dòng)作、愛情、詭異等等)。一個(gè)Set接口用來表示該電影的演員表中的角色集合。"addRole()"方法將元素加入到演員表中,而"getCast()"方法返回一個(gè)不可以更改的集合,該集合中包含演員表。這些方法并不是JDO規(guī)定的,只是為了方便應(yīng)用編程而編寫的。"parseReleaseDate()"方法和"formatReleaseDate()"方法用于將電影的發(fā)行日期標(biāo)準(zhǔn)化(格式化)。為了保持代碼的簡單,如果parseReleaseDate()的參數(shù)格式不對(duì),將會(huì)返回null。

  例1-1 Movie.java

  package com.mediamania.prototype;
  import java.util.Set;
  import java.util.HashSet;
  import java.util.Collections;
  import java.util.Date;
  import java.util.Calendar;
  import java.text.SimpleDateFormat;
  import java.text.ParsePosition;
  public class Movie {
   private static SimpleDateFormat yearFmt = new SimpleDateFormat("yyyy");
   public static final String[] MPAAratings = {
   "G", "PG", "PG-13", "R", "NC-17", "NR"};
   private String title;
   private Date releaseDate;
   private int runningTime;
   private String rating;
   private String webSite;
   private String genres;
   private Set cast; // element type: Role
   private Movie() {}
   public Movie(String title, Date release, int duration, String rating,
   String genres) {
   this.title = title;
   releaseDate = release;
   runningTime = duration;
   this.rating = rating;
   this.genres = genres;
   cast = new HashSet();
   }
   public String getTitle() {
   return title;
   }
   public Date getReleaseDate() {
   return releaseDate;
   }
   public String getRating() {
   return rating;
   }
   public int getRunningTime() {
   return runningTime;
   }
   public void setWebSite(String site) {
   webSite = site;
   }
   public String getWebSite() {
   return webSite;
   }
   public String getGenres() {
   return genres;
   }
   public void addRole(Role role) {
   cast.add(role);
   }
   public Set getCast() {
   return Collections.unmodifiableSet(cast);
   }
   public static Date parseReleaseDate(String val) {
   Date date = null;
   try {
   date = yearFmt.parse(val);
   } catch (java.text.ParseException exc) {}
   return date;
   }
   public String formatReleaseDate() {
   return yearFmt.format(releaseDate);
   }
  }


   JDO對(duì)一個(gè)需要存儲(chǔ)的類強(qiáng)加了一個(gè)要求:一個(gè)無參數(shù)的構(gòu)造器。如果你在類代碼中不定義任何構(gòu)造器,編譯器會(huì)自動(dòng)產(chǎn)生一個(gè)無參數(shù)的構(gòu)造器;而如果你定義了帶參構(gòu)造器,你就必須再定義一個(gè)無參構(gòu)造器,可以將其聲明為private以禁止外部訪問。如果你不定義這個(gè)無參構(gòu)造器,一些JDO產(chǎn)品會(huì)自動(dòng)為你產(chǎn)生一個(gè),但這只是具體的JDO產(chǎn)品提供的功能,是不可移植的。

  例1-2顯示了Actor類的源碼。在我們的目標(biāo)中,所有的演員都有一個(gè)不會(huì)重復(fù)的名字來標(biāo)識(shí)自己,可以是與出生時(shí)的姓名不同的化名;诖,我們用一個(gè)String來表示演員的姓名。每個(gè)演員可能扮演一到多個(gè)角色,類中的"roles"成員表示Actor與Role關(guān)系中Actor的這一邊的屬性。第①行的注釋僅僅為了文檔化,它并不為JDO實(shí)現(xiàn)任何特殊的功能。第②行和第③行“addRole()”和“removeRole()”方法使程序可以維護(hù)某個(gè)Actor實(shí)例和它所關(guān)聯(lián)的Role實(shí)例集。

  例1-2 Actor.java

  package com.mediamania.prototype;
  import java.util.Set;
  import java.util.HashSet;
  import java.util.Collections;
  public class Actor {
   private String name;
 、 private Set roles; // element type: Role
   private Actor() {}
   public Actor(String name) {
   this.name = name;
   roles = new HashSet();
   }
   public String getName() {
   return name;
   }
  ② public void addRole(Role role) {
   roles.add(role);
   }
 、 public void removeRole(Role role) {
   roles.remove(role);
   }
   public Set getRoles() {
   return Collections.unmodifiableSet(roles);
   }
  }


  最后,例1-3給出了Role類的源碼。這個(gè)類代表了Movie類和Actor類之間的關(guān)系,并且包含某個(gè)演員在某部電影中扮演的具體角色的名字。其構(gòu)造器初始化了對(duì)Movie和Actor對(duì)象的引用,并且通過調(diào)用處于關(guān)系的另一端的addRole()方法來保持邏輯一致性。

  例1-3 Role.java

  package com.mediamania.prototype;
  public class Role {
   private String name;
   private Actor actor;
   private Movie movie;
   private Role() {}
   public Role(String name, Actor actor, Movie movie) {
   this.name = name;
   this.actor = actor;
   this.movie = movie;
   actor.addRole(this);
   movie.addRole(this);
   }
   public String getName() {
   return name;
   }
   public Actor getActor() {
   return actor;
   }
   public Movie getMovie() {
   return movie;
   }
  }

  至此,我們已經(jīng)了解了在數(shù)據(jù)庫中有實(shí)例存在的每個(gè)類的源碼。這些類并不需要導(dǎo)入或使用任何JDO相關(guān)的具體類。進(jìn)一步,除了無參的構(gòu)造器,無須任何數(shù)據(jù)或方法來標(biāo)明這些類為可存儲(chǔ)的。用于訪問或更新屬性數(shù)據(jù)并維護(hù)實(shí)例間的關(guān)系的代碼與大多數(shù)Java應(yīng)用中的標(biāo)準(zhǔn)代碼是一模一樣的。

   將類聲明為可存儲(chǔ)的

  為了讓類可以存儲(chǔ),必須指明哪些類是需要存儲(chǔ)的,并且需要提供任何與具體存儲(chǔ)細(xì)節(jié)相關(guān),而Java代碼中又無法體現(xiàn)的信息。JDO使用一個(gè)XML格式的元數(shù)據(jù)文件(metadata,參見術(shù)語表)來描述這些信息。

   你可以基于類(多個(gè)文件)或包(一個(gè)文件)來定義XML格式的元數(shù)據(jù)文件。如果是基于類的,文件名與該類的名稱相同(譯者注:不包含包名),只是擴(kuò)展名以".jdo"結(jié)尾。因此,描述Movie類的元數(shù)據(jù)文件需要命名為"Movie.jdo"并且與編譯生成的Movie.class放置在同一個(gè)目錄中。如果選用基于包的元數(shù)據(jù)文件,則其中包含該包下的多個(gè)類以及多個(gè)下級(jí)包(sub-package)。例1-4給出了對(duì)Media Mania公司的對(duì)象模型進(jìn)行描述的元數(shù)據(jù)。這個(gè)元數(shù)據(jù)基于這個(gè)對(duì)象模型所在的包,并且寫入文件"com/mediamania/prototype/package.jdo"中。

  例1-4 …/prototype/package.jdo文件中的JDO元數(shù)據(jù)

   <?xml version="1.0" encoding="UTF-8" ?>
 、 <!DOCTYPE jdo PUBLIC
  "-//Sun Microsystems, Inc.//DTD Java Data Objects Metadata 1.0//EN"
  "http://java.sun.com/dtd/jdo_1_0.dtd">
   <jdo>
 、 <package name="com.mediamania.prototype" >
 、 <class name="Movie" >
  ④ <field name="cast" >
 、 <collection element-type="Role"/>
   </field>
   </class>
 、 <class name="Role" />
   <class name="Actor" >
   <field name="roles" >
   <collection element-type="Role"/>
   </field>
   </class>
   </package>
   </jdo>

  第①行中指明的"jdo_1_0.dtd"文件提供了對(duì)JDO元數(shù)據(jù)文件中用到的元素的定義。這個(gè)文檔類型定義(DTD)是由JDO規(guī)范所規(guī)定的,必須由一個(gè)JDO產(chǎn)品附帶提供。該文件也可以在http://java.sun.com/dtd下載。你也可以將"DOCTYPE"行中的內(nèi)容改為指向你本地文件系統(tǒng)中的一個(gè)副本文件。

  元數(shù)據(jù)文件可以包含與一個(gè)或多個(gè)含有可存儲(chǔ)類的包的關(guān)于一些存儲(chǔ)細(xì)節(jié)方面的信息。每個(gè)包由一個(gè)"package"元素進(jìn)行定義,該元素具有一個(gè)"name"屬性來表示該包的名稱。第②行給出了我們的com.mediamania.prototype包的對(duì)應(yīng)包元素。在該包元素內(nèi),是各個(gè)該包中的類元素。(如第③行就是Movie類的描述元素)。這個(gè)文件中可以順序?qū)懭攵鄠(gè)包元素,它們不能互相嵌套。

  如果某個(gè)屬性的存儲(chǔ)細(xì)節(jié)信息必須額外指出,那么需要在"class"元素內(nèi)部加入一個(gè)"field"元素,見第④行。比如,你可以通過這個(gè)字段元素標(biāo)明一個(gè)集合類型的屬性中放置的是什么樣的元素類型。這個(gè)元素不是必須的,但加入這個(gè)元素可以更有效地、更準(zhǔn)確地完成映射。Movie類有一個(gè)集合(Set)類型的屬性:cast,而Actor類也有一個(gè)集合類型的屬性:roles;它們都包含對(duì)Role的引用。第⑤行標(biāo)明了cast的元素類型。在多數(shù)情況下,在元數(shù)據(jù)中某屬性的默認(rèn)值被假定為最常用的值(比如Collection類型的屬性的元素類型會(huì)被默認(rèn)為Object)。

  所有的可以存儲(chǔ)的屬性在默認(rèn)情況下會(huì)被視為需存儲(chǔ)的(即具有持續(xù)性)。"static"和"final"的屬性則不能設(shè)置為需存儲(chǔ)的。一個(gè)"transient"的屬性在默認(rèn)情況下不被認(rèn)為是需存儲(chǔ)的,但可以顯式地在元數(shù)據(jù)中將其標(biāo)明為需存儲(chǔ)的。第四章將詳細(xì)闡述此問題。

  第四、十、十二和十三章會(huì)詳細(xì)描述你可以對(duì)類和類中的屬性進(jìn)行哪些特性的描述。而對(duì)于一個(gè)非常簡單的象"Role"一樣的沒有什么集合類型的屬性的類來說,你可以僅僅將這個(gè)類在元數(shù)據(jù)中列出來,如第⑥所示,只要這個(gè)類不需要什么特別的與默認(rèn)情況不同的說明。

  項(xiàng)目編譯環(huán)境

  在本節(jié)中,我們將查看一下用于編譯和運(yùn)行我們的JDO應(yīng)用程序的開發(fā)環(huán)境。這包括項(xiàng)目的文件目錄結(jié)構(gòu),編譯所需要的相關(guān)的jar文件,以及對(duì)可存儲(chǔ)的類進(jìn)行增強(qiáng)(Enhance,參見術(shù)語表)的語法(我們將在本節(jié)的后面部分詳細(xì)說明類的增強(qiáng)這個(gè)概念)。這個(gè)環(huán)境的建立一般與你所具體使用的JDO產(chǎn)品有關(guān),所以你實(shí)際的項(xiàng)目開發(fā)環(huán)境及相關(guān)目錄結(jié)構(gòu)可能會(huì)稍有不同。

  你可以使用Sun公司提供的JDO參考產(chǎn)品(Reference Implementation,是Sun在提出JDO規(guī)范的同時(shí)給出的一個(gè)實(shí)現(xiàn)規(guī)范的簡單產(chǎn)品,用于給其它JDO廠商提供參考,也可以直接作為JDO產(chǎn)品使用,只是性能方便可能很差。這一方面有點(diǎn)類似于Sun的隨J2EE規(guī)范一同發(fā)布的J2EE開發(fā)包中的樣本服務(wù)器),也可以根據(jù)自己的需要選擇其它的JDO產(chǎn)品。本書中的例子均基于JDO參考產(chǎn)品。你可以在http://www.jcp.org網(wǎng)站上選擇JSR-12,然后便可以下載到這個(gè)參考產(chǎn)品。當(dāng)你安裝了一個(gè)JDO產(chǎn)品后,你需要搭建一個(gè)目錄結(jié)構(gòu),并設(shè)置相應(yīng)的CLASSPATH以包含項(xiàng)目所需要的所有jar文件和相關(guān)的目錄,這樣才能編譯和運(yùn)行你的應(yīng)用程序。

  JDO在你的編譯過程中引入了一個(gè)額外的步驟,稱作類增強(qiáng)(Class Enhancement,參見術(shù)語表)。每個(gè)需要存儲(chǔ)的類必須經(jīng)過增強(qiáng)才能在JDO的運(yùn)行環(huán)境中使用。你的需存儲(chǔ)的類被javac編譯器編譯成一些.class文件,而一個(gè)增強(qiáng)器讀取這些生成的二進(jìn)制代碼文件和對(duì)應(yīng)的元數(shù)據(jù)文件,然后根據(jù)元數(shù)據(jù)文件標(biāo)明的信息將一些額外代碼插入到二進(jìn)制代碼中,從而生成一些新的可以在JDO環(huán)境中運(yùn)行的.class文件。你的JDO應(yīng)用程序只能調(diào)入這些增強(qiáng)過的類文件。JDO參考產(chǎn)品包含了一個(gè)增強(qiáng)器,名為"參考增強(qiáng)器(Reference Enhancer)"。

   JDO參考產(chǎn)品需要的jar文件

  當(dāng)你采用JDO參考產(chǎn)品后,你需要在開發(fā)過程中將下列jar文件放到你的CLASSPATH中。在運(yùn)行時(shí),所有這些jar文件也必須處于你的CLASSPATH中。

  jdo.jar

   JDO規(guī)范定義的標(biāo)準(zhǔn)幾個(gè)的接口和類。

  jdori.jar

   Sun公司的參考產(chǎn)品的jar文件

  btree.jar

   JDO參考產(chǎn)品所用到的軟件,用于管理文件中存儲(chǔ)的數(shù)據(jù)。JDO參考產(chǎn)品采用一個(gè)文件來保存數(shù)據(jù)對(duì)象。

  jta.jar

   Java的事務(wù)控制API。其中包含javax.transaction包中定義的Synchronization接口,在JDO接口中會(huì)使用到。這個(gè)jar文件中包含的其它一些工具類一般來說在一個(gè)JDO產(chǎn)品中會(huì)很有用。你可以在http://java.sun.com/products/jta/index.html上下載這個(gè)jar文件

  antlr.jar

   JDO參考產(chǎn)品解析JDO的查詢語言(即JDOQL,參見術(shù)語表)中用到的語法分析技術(shù)相關(guān)文件。參考產(chǎn)品采用了Antlr 2.7.0。你可以在http://www.antlr.org上下載。

  xerces.jar

   參考產(chǎn)品在解析XML文件(主要是元數(shù)據(jù)文件)所使用的Xerces-J 1.4.3版本。該文件可以在http://xml.apache.org/xerces-j/上下載。

  前三個(gè)文件是包含在JDO參考產(chǎn)品中的;后三個(gè)文件可以從各自的網(wǎng)站上下載。

  參考產(chǎn)品還包含一個(gè)jar文件:jdo-enhancer.jar,其中包括參考增強(qiáng)器。其中的所有類在jdori.jar文件中也有。多數(shù)情況下,你會(huì)在開發(fā)環(huán)境和運(yùn)行環(huán)境都使用jdori.jar,不需要jdori-enhancer.jar文件。jdori-enhancer.jar文件被單獨(dú)打包原因是這樣一來你可以獨(dú)立于具體使用的JDO產(chǎn)品而對(duì)類代碼進(jìn)行增強(qiáng)。除參考產(chǎn)品之外,一些其它的產(chǎn)品也會(huì)將這個(gè)jar文件與產(chǎn)品一起發(fā)布。

  如果你使用了其它的JDO產(chǎn)品,它的文檔會(huì)告訴你所需要的jar文件的列表。一個(gè)產(chǎn)品通常將所有這些需要的jar文件都放到它安裝時(shí)生成的某個(gè)特別的目錄下。包含JDO的標(biāo)準(zhǔn)接口的jdo.jar文件應(yīng)該被所有的JDO產(chǎn)品所包含,一般情況下,這個(gè)文件都會(huì)在某個(gè)具體廠商的JDO產(chǎn)品中存在。JDOCentral(http://www.jdocentral.com)提供大量的JDO資源,包括很多商用JDO產(chǎn)品的免費(fèi)試用版下載。

  項(xiàng)目目錄結(jié)構(gòu)

  對(duì)于Media Mania應(yīng)用開發(fā)環(huán)境來說,你需要采用下面的目錄結(jié)構(gòu),這個(gè)項(xiàng)目必須有一個(gè)根目錄,存在于系統(tǒng)的文件體系的某個(gè)地方。下面這些目錄都是以這個(gè)根目錄為基準(zhǔn)的:

  src

   這個(gè)目錄包括應(yīng)用的所有源碼。在src目錄下,有一個(gè)按照com/mediamania/prototype結(jié)構(gòu)的子目錄體系(與Java中的com.mediamania.prototype包相對(duì)應(yīng))。這也是Movie.java、Actor.java和Role.java源文件所在的目錄。

  classes

   當(dāng)Java源碼被編譯時(shí),生成的.class文件置于這個(gè)目錄中

  enhanced

   這個(gè)目錄存放增強(qiáng)后的.class類代碼文件(由增強(qiáng)器所產(chǎn)生)

  database

   這個(gè)目錄存放JDO參考產(chǎn)品用于存儲(chǔ)數(shù)據(jù)的文件。

  盡管這樣的目錄結(jié)構(gòu)并不是JDO規(guī)范所要求的,但你得理解它,這樣才能跟隨我們對(duì)Media Mania應(yīng)用的描述。

  當(dāng)你執(zhí)行你的JDO應(yīng)用時(shí),Java運(yùn)行環(huán)境必須調(diào)入增強(qiáng)版本的類文件,也就是處于enhanced目錄中的類文件。因此,在你的CLASSPATH中這個(gè)目錄必須處于classes目錄之前。作為一種可選方案,你也可以采用就地增強(qiáng),用你的增強(qiáng)后的類文件直接替換未增強(qiáng)的文件。

   增強(qiáng)類代碼以便存儲(chǔ)

  類在其實(shí)例被JDO環(huán)境處理之前必須先被增強(qiáng)。JDO增強(qiáng)器在你的類中加入額外的數(shù)據(jù)和方法,使其實(shí)例可以被JDO產(chǎn)品處理。增強(qiáng)器先從由javac編譯器所產(chǎn)生的類文件中讀取信息,再根據(jù)元數(shù)據(jù)來生成新的增強(qiáng)過的包含必要功能的類文件。JDO規(guī)范化了增強(qiáng)器所做的改變,使得增強(qiáng)后的類文件具有二進(jìn)制兼容性,可以在其它的JDO產(chǎn)品中使用。這些增強(qiáng)后的文件也獨(dú)立于任何具體的數(shù)據(jù)庫。

  前面已經(jīng)提到,Sun公司提供的JDO參考產(chǎn)品中的增強(qiáng)器稱作參考增強(qiáng)器。而JDO產(chǎn)品廠商一般可能會(huì)提供自己的增強(qiáng)器;在命令行調(diào)用增強(qiáng)器的語法可能會(huì)與這里提到的有所不同。每個(gè)產(chǎn)品都會(huì)向你提供文檔以闡釋如果在該產(chǎn)品上對(duì)你的類進(jìn)行增強(qiáng)。

  例1-5給出了使用參考增強(qiáng)器對(duì)我們的Media Mania應(yīng)用的類進(jìn)行增強(qiáng)的命令行。"-d"參數(shù)指明將要存放增強(qiáng)后的類文件的目錄,我們已經(jīng)計(jì)劃放到enhanced目錄下。增強(qiáng)器接收一系列JDO元數(shù)據(jù)文件和一系列需要增強(qiáng)的類文件作參數(shù)。目錄之間的分隔符和續(xù)行符(line-continuation)可能會(huì)不一樣,這依賴于你進(jìn)行編譯的操作系統(tǒng)。

  例1-5 對(duì)類進(jìn)行增強(qiáng)

  java com.sun.jdori.enhancer.Main -d enhanced
   classes/com/mediamania/prototype/package.jdo
   classes/com/mediamania/prototype/Movie.class
   classes/com/mediamania/prototype/Actor.class
   classes/com/mediamania/prototype/Role.class

  盡管將元數(shù)據(jù)文件與源代碼放在一起會(huì)比較方便,JDO規(guī)范還是推薦元數(shù)據(jù)文件可以作為與類文件一起作為資源被類載入器(ClassLoader)調(diào)入。元數(shù)據(jù)在編譯時(shí)和運(yùn)行時(shí)都需要,所以,我們將package.jdo元數(shù)據(jù)文件放在classes目錄體系中的prototype包的目錄中。

  在例1-5中,我們的對(duì)象模型中的所有.class類文件被一起列出,但你也可以將每個(gè)類文件單獨(dú)增強(qiáng)。當(dāng)這個(gè)增強(qiáng)命令執(zhí)行時(shí),它將增強(qiáng)后的新文件放到enhanced目錄下。

  創(chuàng)建數(shù)據(jù)庫連接和事務(wù)

  現(xiàn)在既然我們的類已經(jīng)被增強(qiáng)了,它們的實(shí)例也就可以被儲(chǔ)存到數(shù)據(jù)庫中了。我們現(xiàn)在來看看應(yīng)用中如果創(chuàng)建一個(gè)與數(shù)據(jù)庫的連接并在一個(gè)事務(wù)(Transaction)中執(zhí)行一些操作。我們開始寫直接使用JDO接口的軟件代碼,所有的在應(yīng)用中用到的JDO接口都定義在javax.jdo包中。

  JDO中有一個(gè)接口叫做PersistenceManager(存儲(chǔ)管理器,見術(shù)語表),它具有一個(gè)到數(shù)據(jù)庫的連接。一個(gè)PersistenceManager還有一個(gè)JDO中的Transaction(事務(wù))接口的實(shí)例,用于控制一個(gè)事務(wù)的開始和結(jié)束。這個(gè)Transaction實(shí)例的獲取方式是調(diào)用PersistenceManager實(shí)例的currentTransaction()方法。

  獲取一個(gè)PersistenceManager

   PersistenceManagerFactory(存儲(chǔ)管理器工廠,見術(shù)語表)用來配置和獲取PersistenceManager。PersistenceManagerFactory中的方法用來設(shè)置一些配置屬性,這些配置屬性控制了從中獲得的PersistenceManager實(shí)例的行為。于是,一個(gè)JDO應(yīng)用的第一步便是獲取一個(gè)PersistenceManagerFactory實(shí)例。要取得這個(gè)實(shí)例,需要調(diào)用下面的JDOHelper類的靜態(tài)方法:

  static PersistenceManagerFactory getPersistenceManagerFactory(Properties props);

  這個(gè)Properties實(shí)例可以通過程序設(shè)置,也可以從文件中讀取。例1-6列出了我們將在Media Mania應(yīng)用中用到的配置文件的內(nèi)容。其中,第①行中的PersistenceManagerFactoryClass屬性通過提供具體JDO產(chǎn)品的PersistenceManagerFactory接口實(shí)現(xiàn)類來指明采用哪個(gè)JDO產(chǎn)品。在本例中,我們指明Sun公司的JDO參考產(chǎn)品所定義的類。例1-6中列出的其它的屬性包括用于連接到特定的數(shù)據(jù)庫的連接URL和用戶名/密碼,這些一般都是連接到具體的數(shù)據(jù)庫所需要的。

  例1-6 jdo.properties文件內(nèi)容

 、 javax.jdo.PersistenceManagerFactoryClass=com.sun.jdori.fostore.FOStorePMF
   javax.jdo.option.ConnectionURL=fostore:database/fostoredb
   javax.jdo.option.ConnectionUserName=dave
   javax.jdo.option.ConnectionPassword=jdo4me
   javax.jdo.option.Optimistic=false

  這個(gè)連接URL的格式依賴于采用的具體的數(shù)據(jù)庫。在JDO參考產(chǎn)品中包括它自己的存儲(chǔ)機(jī)制,稱作"文件對(duì)象數(shù)據(jù)庫File Object Store"(FOStore)。例1-6中的ConnectionURL屬性標(biāo)明了實(shí)際使用的數(shù)據(jù)庫位于database目錄中,在我們的項(xiàng)目的根目錄下。在本例中,我們提供了一個(gè)相對(duì)路徑;但提供絕對(duì)路徑也是可以的。這個(gè)URL同時(shí)指明了FOStore數(shù)據(jù)庫文件名稱將以"fostoredb"開頭。

  如果你使用了別的JDO產(chǎn)品,你需要對(duì)以上這些屬性提供另外的值,可能你還得提供一些額外的屬性。請參閱該產(chǎn)品的文檔以獲取需要配置的必要的屬性的說明。

  創(chuàng)建一個(gè)FOStore數(shù)據(jù)庫

  要使用FOStore我們必須先創(chuàng)建一個(gè)數(shù)據(jù)庫。例1-7中的程序利用jdo.properties文件創(chuàng)建一個(gè)數(shù)據(jù)庫;所有的應(yīng)用都使用這個(gè)配置文件。第①行將這些配置屬性從jdo.properties文件中調(diào)入到一個(gè)Properties實(shí)例中。該程序的第②行加入了一個(gè)"com.sun.jdori.option.ConnectionCreate"屬性以指明數(shù)據(jù)庫需要?jiǎng)?chuàng)建。將其設(shè)為true,就能引導(dǎo)參考產(chǎn)品創(chuàng)建該數(shù)據(jù)庫。我們在第③行調(diào)用getPersistenceManagerFactory()來獲取PersistenceManagerFactory。第④行生成一個(gè)PersistenceManager。

  為完成數(shù)據(jù)庫的創(chuàng)建,我們還需要開始并結(jié)束一個(gè)事務(wù)。第⑤行中調(diào)用了PersistenceManager的currentTransaction()方法來訪問與該P(yáng)ersistenceManager相關(guān)聯(lián)的Transaction實(shí)例。第⑥行和第⑦行調(diào)用這個(gè)Transaction實(shí)例的begin()和commit()方法來開始和結(jié)束一個(gè)事務(wù)。當(dāng)你執(zhí)行這個(gè)程序時(shí),在database目錄下就會(huì)生成一個(gè)FOStore數(shù)據(jù)庫,包括兩個(gè)文件:fostore.btd和fostore.btx.

  例1-7 創(chuàng)建一個(gè)FOStore數(shù)據(jù)庫

  package com.mediamania;
  import java.io.FileInputStream;
  import java.io.InputStream;
  import java.util.Properties;
  import javax.jdo.JDOHelper;
  import javax.jdo.PersistenceManagerFactory;
  import javax.jdo.PersistenceManager;
  import javax.jdo.Transaction;
  public class CreateDatabase {
   public static void main(String[] args)) {
   create();
   }
   public static void create() {
   try {
   InputStream propertyStream = new FileInputStream("jdo.properties");
   Properties jdoproperties = new Properties();
 、 jdoproperties.load(propertyStream);
 、 jdoproperties.put("com.sun.jdori.option.ConnectionCreate", "true");
   PersistenceManagerFactory pmf =
 、 JDOHelper.getPersistenceManagerFactory(jdoproperties);
 、 PersistenceManager pm = pmf.getPersistenceManager();
 、 Transaction tx = pm.currentTransaction();
 、 tx.begin();
  ⑦ tx.commit();
   } catch (Exception e) {
   System.err.println("Exception creating the database");
   e.printStackTrace();
   System.exit( -1);
   }
   }
  }

   JDO參考產(chǎn)品提供了這種程序化創(chuàng)建FODatastore數(shù)據(jù)庫的方式,而大多數(shù)數(shù)據(jù)庫都提供一個(gè)獨(dú)立于JDO的工具來創(chuàng)建數(shù)據(jù)庫。JDO并不規(guī)定一個(gè)與廠商無關(guān)的接口來創(chuàng)建數(shù)據(jù)庫。數(shù)據(jù)庫的創(chuàng)建一般都與具體使用的數(shù)據(jù)庫相關(guān)。本程序中顯示了在FOStore數(shù)據(jù)庫中是如何完成這一步的。

  另外,如果你在關(guān)系數(shù)據(jù)庫上使用JDO,某些情況下可以有一個(gè)額外的步驟:根據(jù)對(duì)象模型創(chuàng)建或者將對(duì)象模型映射到一個(gè)現(xiàn)存的數(shù)據(jù)庫模式(shema,即某數(shù)據(jù)庫用戶及其所擁有的數(shù)據(jù)表體系的合稱)。創(chuàng)建一個(gè)數(shù)據(jù)庫模式的過程與你采用的具體JDO產(chǎn)品的有關(guān),你需要查看該產(chǎn)品的文檔來決定采取必要的步驟。

   對(duì)實(shí)例的操作

  至此我們已經(jīng)有了一個(gè)可以存放數(shù)據(jù)類的實(shí)例的數(shù)據(jù)庫,每個(gè)程序需要獲得一個(gè)PersistenceManager來訪問或更新該數(shù)據(jù)庫。例1-8給出了MediaManiaApp類的源碼,這個(gè)類是本書中的每個(gè)應(yīng)用程序的基礎(chǔ)類,每個(gè)程序是在execute()方法中實(shí)現(xiàn)了具體的業(yè)務(wù)邏輯的一個(gè)具體的子類(Concrete子類,相對(duì)于抽象Abstract而言)。

  MediaManiaApp有一個(gè)構(gòu)造器用來從jdo.properties中讀取配置信息(行①)。從該文件調(diào)入配置信息后,它調(diào)用getPropertyOverrides()方法并且合并成最終的屬性集(properties)到j(luò)doproperties對(duì)象中。一個(gè)程序子類可以重載getPropertyOverrides()來提供額外的配置信息或者更改jdo.properties文件中給出的配置。這個(gè)構(gòu)造器獲取一個(gè)PersistenceManagerFactory(行②),然后獲取一個(gè)PersistenceManager(行③)。我們還提供一個(gè)getPersistenceManager()方法以便在MediaManiaApp類之外獲取PersistenceManager。與PersistenceManager關(guān)聯(lián)的Transaction在第④行獲取。

  各個(gè)程序子類調(diào)用一個(gè)在MediaManiaApp類中定義的executeTransaction()方法,這個(gè)方法在行⑤中開始一個(gè)事務(wù),然后在行⑥中調(diào)用execute()方法,也即執(zhí)行子類中的具體功能的方法。
  我們選擇了一個(gè)特別的程序類的設(shè)計(jì)來簡化和減少創(chuàng)建一個(gè)可運(yùn)行的環(huán)境的冗余代碼。這些并不是JDO所要求的,你也可以根據(jù)自己的應(yīng)用程序環(huán)境選擇最為合適的方式。

  當(dāng)(子類中實(shí)現(xiàn)的)execute()方法返回后,我們會(huì)嘗試提交這個(gè)事務(wù)(行⑦),而如果有任何異常發(fā)生的話,我們會(huì)回滾(rollback)這個(gè)事務(wù)并將異常信息打印到系統(tǒng)錯(cuò)誤輸出流中(System.err)。

  例1-8 MediaManiaApp基類

  package com.mediamania;
  import java.io.FileInputStream;
  import java.io.InputStream;
  import java.util.Properties;
  import java.util.Map;
  import java.util.HashMap;
  import javax.jdo.JDOHelper;
  import javax.jdo.PersistenceManagerFactory;
  import javax.jdo.PersistenceManager;
  import javax.jdo.Transaction;
  public abstract class MediaManiaApp {
   protected PersistenceManagerFactory pmf;
   protected PersistenceManager pm;
   protected Transaction tx;
   public abstract void execute(); //defined in concrete application subclasses
   protected static Map getPropertyOverrides() {
   return new HashMap();
   }
   public MediaManiaApp() {
   try {
   InputStream propertyStream = new FileInputStream("jdo.properties");
   Properties jdoproperties = new Properties();
 、 jdoproperties.load(propertyStream);
   jdoproperties.putAll(getPropertyOverrides());
 、 pmf = JDOHelper.getPersistenceManagerFactory(jdoproperties);
 、 pm = pmf.getPersistenceManager();
 、 tx = pm.currentTransaction();
   } catch (Exception e) {
   e.printStackTrace(System.err);
   System.exit( -1);
   }
   }
   public PersistenceManager getPersistenceManager() {
   return pm;
   }
   public void executeTransaction() {
   try {
  ⑤ tx.begin();
 、 execute();
 、 tx.commit();
   } catch (Throwable exception) {
   exception.printStackTrace(System.err);
   if (tx.isActive())
   tx.rollback();
   }
   }
  }

  存儲(chǔ)實(shí)例

  我們來看看一個(gè)簡單的程序,名為CreateMovie,用于存儲(chǔ)一個(gè)Movie實(shí)例,如例1-9所示。該的功能被放在execute()方法中。構(gòu)造一個(gè)CreateMovie的實(shí)例后,我們調(diào)用MediaManiaApp基類中定義的executeTransaction()方法,它會(huì)調(diào)用本類中重載過的execute()方法。這個(gè)execute()方法中行⑤初始化一個(gè)單獨(dú)的Movie實(shí)例,然后在行⑥調(diào)用PersistenceManager的makePersistent()方法保存這個(gè)實(shí)例。如果這個(gè)事務(wù)成功提交(commit),這個(gè)Movie實(shí)例就會(huì)被存儲(chǔ)到數(shù)據(jù)庫中。

  例1-9 創(chuàng)建一個(gè)Movie實(shí)例并保存它

  package com.mediamania.prototype;
  import java.util.Calendar;
  import java.util.Date;
  import com.mediamania.MediaManiaApp;
  public class CreateMovie extends MediaManiaApp {
   public static void main(String[] args)) {
   CreateMovie createMovie = new CreateMovie();
   createMovie.executeTransaction();
   }
   public void execute() {
   Calendar cal = Calendar.getInstance();
   cal.clear();
   cal.set(Calendar.YEAR, 1997);
   Date date = cal.getTime();
 、 Movie movie = new Movie("Titanic", date, 194, "PG-13",
   "historical,drama");
 、 pm.makePersistent(movie);
   }
  }

  現(xiàn)在我們來看一個(gè)更大的應(yīng)用程序:LoadMovies,如例1-10中所示,它從一個(gè)包含電影信息的文件中讀取并創(chuàng)建多個(gè)Movie實(shí)例。這個(gè)信息文件名作為參數(shù)傳遞到程序中,LoadMovies構(gòu)造器初始化一個(gè)BufferedReader來讀取信息。execute()方法通過調(diào)用parseMovieDate()每次從這個(gè)文件讀取一行并分析之,從而在行①創(chuàng)建一個(gè)Movie實(shí)例,并在行②保存之。當(dāng)這個(gè)事務(wù)在executeTransaction()中提交時(shí),所有新創(chuàng)建的Movie實(shí)例都會(huì)被保存到數(shù)據(jù)庫中。

  例1-10 LoadMovies

  package com.mediamania.prototype;
  import java.io.FileReader;
  import java.io.BufferedReader;
  import java.util.Calendar;
  import java.util.Date;
  import java.util.StringTokenizer;
  import javax.jdo.PersistenceManager;
  import com.mediamania.MediaManiaApp;
  public class LoadMovies extends MediaManiaApp {
   private BufferedReader reader;
   public static void main(String[] args)) {
   LoadMovies loadMovies = new LoadMovies(args[0]);
   loadMovies.executeTransaction();
   }
   public LoadMovies(String filename) {
   try {
   FileReader fr = new FileReader(filename);
   reader = new BufferedReader(fr);
   } catch (Exception e) {
   System.err.print("Unable to open input file ");
   System.err.println(filename);
   e.printStackTrace();
   System.exit( -1);
   }
   }
   public void execute() {
   try {
   while (reader.ready()) {
   String line = reader.readLine();
   parseMovieData(line);
   }
   } catch (java.io.IOException e) {
   System.err.println("Exception reading input file");
   e.printStackTrace(System.err);
   }
   }
   public void parseMovieData(String line) {
   StringTokenizer tokenizer new StringTokenizer(line, ";");
   String title = tokenizer.nextToken();
   String dateStr = tokenizer.nextToken();
   Date releaseDate = Movie.parseReleaseDate(dateStr);
   int runningTime = 0;
   try {
   runningTime = Integer.parseInt(tokenizer.nextToken());
   } catch (java.lang.NumberFormatException e) {
   System.err.print("Exception parsing running time for ");
   System.err.println(title);
   }
   String rating = tokenizer.nextToken();
   String genres = tokenizer.nextToken();
 、 Movie movie = new Movie(title, releaseDate, runningTime, rating,
   genres);
  ② pm.makePersistent(movie);
   }
  }

  電影信息文件中的數(shù)據(jù)格式是:

  movie title;release date;running time;movie rating;genre1,genre2,genre3

  其中用于表示發(fā)行日期的數(shù)據(jù)格式由Movie類來控制,因此parseReleaseDate()被調(diào)用以根據(jù)發(fā)行日期數(shù)據(jù)產(chǎn)生一個(gè)Date實(shí)例。一部電影可以屬于多種風(fēng)格,在數(shù)據(jù)行的尾部列出。

  訪問實(shí)例

  現(xiàn)在讓我們來訪問數(shù)據(jù)庫中的Movie實(shí)例以驗(yàn)證我們已經(jīng)成功地將它們保存。在JDO中有很多方式可以訪問實(shí)例:
  從一個(gè)類的擴(kuò)展(Extent,表示一個(gè)類及其所有子類)中迭代(iterate,參見術(shù)語表)
  通過對(duì)象模型來瀏覽(navigate)
  執(zhí)行一個(gè)查詢
  extent(擴(kuò)展)是用來訪問某個(gè)類及其所有子類的工具。而如果程序中只想訪問其中的部分實(shí)例,可以執(zhí)行一個(gè)查詢,在查詢中通過過濾條件(filter)規(guī)定必須滿足的一個(gè)布爾型的斷言(即判斷語句)來限制返回的實(shí)例。當(dāng)程序從數(shù)據(jù)庫中訪問到一個(gè)實(shí)例之后,便可以通過在對(duì)象模型該實(shí)例相關(guān)的對(duì)其它實(shí)例的引用或?qū)ζ渌鼘?shí)例集合的遍歷來瀏覽其它的實(shí)例。這些實(shí)例在被訪問到之前不會(huì)從數(shù)據(jù)庫調(diào)入到內(nèi)存。以上這些訪問實(shí)例的方式常常被結(jié)合起來使用,JDO保證在一個(gè)PersistenceManager中每個(gè)實(shí)例在內(nèi)存中只會(huì)有一個(gè)副本。每個(gè)PersistenceManager控制一個(gè)單獨(dú)的事務(wù)上下文(transaction context)。

   遍歷一個(gè)類擴(kuò)展

   JDO提供了Extent接口來訪問一個(gè)類的擴(kuò)展。這個(gè)擴(kuò)展允許對(duì)一個(gè)類的所有實(shí)例進(jìn)行訪問,但并不表示所有的實(shí)例都在內(nèi)存中。下面的例1-11給出的PrintMovies程序就采用了Movie類的擴(kuò)展。

  例1-11 遍歷Movie類的擴(kuò)展

  package com.mediamania.prototype;
  import java.util.Iterator;
  import java.util.Set;
  import javax.jdo.PersistenceManager;
  import javax.jdo.Extent;
  import com.mediamania.MediaManiaApp;
  public class PrintMovies extends MediaManiaApp {
   public static void main(String[] args)) {
   PrintMovies movies = new PrintMovies();
   movies.executeTransaction();
   }
   public void execute() {
 、 Extent extent = pm.getExtent(Movie.class, true);
 、 Iterator iter = extent.iterator();
   while (iter.hasNext()) {
 、 Movie movie = (Movie) iter.next();
   System.out.print(movie.getTitle());
   System.out.print(";");
   System.out.print(movie.getRating());
   System.out.print(";");
   System.out.print(movie.formatReleaseDate());
   System.out.print(";");
   System.out.print(movie.getRunningTime());
   System.out.print(";");
  ④ System.out.println(movie.getGenres());
 、 Set cast = movie.getCast();
   Iterator castIterator = cast.iterator();
   while (castIterator.hasNext()) {
 、 Role role = (Role) castIterator.next();
   System.out.print(" ");
   System.out.print(role.getName());
   System.out.print(",");
 、 System.out.println(role.getActor().getName());
   }
   }
  ⑧ extent.close(iter);
   }
  }

  第①行中我們從PersistenceManager獲取一個(gè)Movie類的擴(kuò)展,第二個(gè)參數(shù)表示是否希望包含Movie類的所有子類,false使得只有Movie類的實(shí)例被返回,即便是還有其它的Movie的子類的實(shí)例存在。盡管我們目前還沒有任何Movie的子類,以true作參數(shù)將保證我們將來可能加入的類似的Movie的子類的實(shí)例也被返回。Extent接口有一個(gè)iterator()方法,即我們在行②中調(diào)用以獲取一個(gè)Iterator遍歷器來逐個(gè)訪問這個(gè)類擴(kuò)展中的每個(gè)實(shí)例。行③采用遍歷器來訪問Movie類的實(shí)例。程序在后面就可以針對(duì)Movie的實(shí)例進(jìn)行操作來取得數(shù)據(jù)并打印出來。例如:行④中我們調(diào)用getGenres()來取得一部電影所屬的風(fēng)格,行⑤中我們?nèi)〉秒娪爸械慕巧,在行⑥中取得每個(gè)角色并打印其名稱,行⑦中我們通過調(diào)用getActor()來瀏覽該角色的演員對(duì)象,這是我們在Role類中已經(jīng)定義好的,我們打印了該演員的姓名。

   當(dāng)這個(gè)程序結(jié)束對(duì)類擴(kuò)展的遍歷之后,行⑧中關(guān)閉這個(gè)遍歷器來釋放執(zhí)行這個(gè)遍歷時(shí)占用的相關(guān)資源。對(duì)一個(gè)擴(kuò)展可以同時(shí)進(jìn)行多個(gè)遍歷,這個(gè)close()方法關(guān)閉一個(gè)特定的遍歷器,而closeAll()方法可以關(guān)閉與一個(gè)類擴(kuò)展相關(guān)聯(lián)的所有遍歷器。

  瀏覽對(duì)象模型

   例1-11演示了對(duì)Movie類擴(kuò)展的遍歷。但在行⑥中我們也根據(jù)對(duì)象模型瀏覽了一部電影相關(guān)的角色集合。行⑦中我們也通過Role實(shí)例中的引用訪問了相關(guān)的演員實(shí)例。行⑤和行⑦分別顯示了對(duì)"對(duì)多(to-many)"和"對(duì)一(to-one)"的關(guān)系的訪問(traversal)。從一個(gè)類到另一個(gè)類的關(guān)系具有一個(gè)重?cái)?shù)(cardinality,表示可能發(fā)生關(guān)聯(lián)的目標(biāo)對(duì)象總數(shù)),表示與一個(gè)或多個(gè)實(shí)例進(jìn)行關(guān)聯(lián)。一個(gè)引用表示在重?cái)?shù)為一的情況;而一個(gè)集合用于關(guān)聯(lián)多個(gè)對(duì)象(重?cái)?shù)為多)。

   訪問相關(guān)聯(lián)的實(shí)例所需要的語法與在內(nèi)存中對(duì)關(guān)聯(lián)對(duì)象的標(biāo)準(zhǔn)瀏覽方式是完全一樣的。在行③和行⑦之間程序并不需要調(diào)用任何JDO的接口,它僅僅是在對(duì)象中通過關(guān)系來遍歷(traverse)。相關(guān)的實(shí)例直到被程序直接訪問到時(shí)才會(huì)被從數(shù)據(jù)庫讀入并在內(nèi)存中生成。對(duì)數(shù)據(jù)庫的訪問是透明的,實(shí)例即需即調(diào)。某些JDO產(chǎn)品還提供Java接口之外的機(jī)制讓你調(diào)節(jié)對(duì)該JDO產(chǎn)品的緩沖的訪問機(jī)制。你的Java程序獨(dú)立于這些優(yōu)化之外,但可以從這些優(yōu)化中獲得運(yùn)行性能上的改善。

  在JDO環(huán)境中訪問相關(guān)的數(shù)據(jù)庫對(duì)象的方式與在非JDO的環(huán)境訪問臨時(shí)(transient)對(duì)象的方式是一樣的,因此你可以按照非JDO的方式編寫你的軟件,F(xiàn)有的沒有任何針對(duì)JDO或其它方面的存儲(chǔ)因素的考慮的軟件可以通過JDO來完成對(duì)數(shù)據(jù)庫中的實(shí)例對(duì)象的瀏覽。這個(gè)特點(diǎn)極大地推動(dòng)了開發(fā)生產(chǎn)力,也允許現(xiàn)有的軟件可以快速、方便地集成到JDO環(huán)境中。

  執(zhí)行查詢

   在一個(gè)類擴(kuò)展的基礎(chǔ)上也可以運(yùn)行一個(gè)查詢。JDO中的Query接口用來選取符合某些條件的實(shí)例子集。本章中剩下的例子需要按照給定的唯一名稱訪問指定的Actor或Movie對(duì)象。這些方法(參見例1-12)大同小異;getActor()執(zhí)行一個(gè)基于姓名的查詢,而getMovie()方法執(zhí)行一個(gè)基于片名的查詢。

  例1-12 PrototypeQueries類中的查詢方法

  package com.mediamania.prototype;
  import java.util.Collection;
  import java.util.Iterator;
  import javax.jdo.PersistenceManager;
  import javax.jdo.Extent;
  import javax.jdo.Query;
  public class PrototypeQueries {
   public static Actor getActor(PersistenceManager pm, String actorName) {
  ① Extent actorExtent = pm.getExtent(Actor.class, true);
 、 Query query = pm.newQuery(actorExtent, "name == actorName");
  ③ query.declareParameters("String actorName");
 、 Collection result = (Collection) query.execute(actorName);
   Iterator iter = result.iterator();
   Actor actor = null;
  ⑤ if (iter.hasNext())
   actor = (Actor) iter.next();
 、 query.close(result);
   return actor;
   }
   public static Movie getMovie(PersistenceManager pm, String movieTitle) {
   Extent movieExtent = pm.getExtent(Movie.class, true);
   Query query = pm.newQuery(movieExtent, "title == movieTitle");
   query.declareParameters("String movieTitle");
   Collection result = (Collection) query.execute(movieTitle);
   Iterator iter = result.iterator();
   Movie movie = null;
   if (iter.hasNext())
   movie = (Movie) iter.next();
   query.close(result);
   return movie;
   }
  }
  
  我們來看看getActor()方法。在行①中我們?nèi)〉揭粋(gè)Actor類的擴(kuò)展,行②中通過在PersistenceManager接口中定義的newQuery()方法創(chuàng)建了一個(gè)Query實(shí)例,這個(gè)查詢建立在這個(gè)類擴(kuò)展和相應(yīng)的過濾條件的基礎(chǔ)上。

  在過濾條件中的"name"標(biāo)識(shí)符代表Actor類中的name屬性。用于決定如何解釋這個(gè)標(biāo)識(shí)符的命名空間(namespace)取決于初始化這個(gè)Query實(shí)例的類擴(kuò)展。過濾條件表達(dá)式指明演員的姓名等于actorName,在這個(gè)過濾器中我們可以用"=="號(hào)來直接比較兩個(gè)字符串,而不必使用Java的語法(name.equals(actorName))。
  actorName標(biāo)識(shí)符是一個(gè)查詢參數(shù),在行③中進(jìn)行聲明。一個(gè)查詢參數(shù)讓你在查詢執(zhí)行時(shí)給出一個(gè)值來進(jìn)行查詢。我們選擇同樣的標(biāo)識(shí)符"actorName"來既作為這個(gè)方法的參數(shù)名,又作為查詢的參數(shù)名。這個(gè)查詢在第④行執(zhí)行,以getActor()方法的actorName參數(shù)值作為查詢參數(shù)actorName的值。

  Query.execute()的返回類型被定義為Object,在JDO1.0.1中,返回的類型總是Collection類型,因此我們可以直接將這個(gè)返回對(duì)象強(qiáng)制制轉(zhuǎn)換為一個(gè)Collection。在JDO1.0.1中定義返回Object是為了讓將來可以擴(kuò)展為返回一個(gè)Collection以外的類型。之后,我們的方法在第⑤行試著訪問一個(gè)元素對(duì)象,我們假定對(duì)一個(gè)姓名來說,在數(shù)據(jù)庫中只有單獨(dú)的一個(gè)Actor實(shí)例與之對(duì)應(yīng)。在返回這個(gè)結(jié)果之前,行⑥關(guān)閉這個(gè)查詢以釋放相關(guān)的資源。如果這個(gè)查詢找到了該姓名的演員實(shí)例,則返回之,否則如果查詢結(jié)果是空集蚍禱豱ull。

  更改實(shí)例

  現(xiàn)在我們看看兩個(gè)更改數(shù)據(jù)庫中的實(shí)例的程序。當(dāng)一個(gè)程序在一個(gè)事務(wù)中訪問一個(gè)數(shù)據(jù)庫中的實(shí)例時(shí),它可以更改這個(gè)實(shí)例的一個(gè)或多個(gè)屬性值。而事務(wù)提交時(shí),所有對(duì)這些實(shí)例的更改會(huì)被自動(dòng)地全部同步到數(shù)據(jù)庫中去。

  例1-13中給出的UpdateWebSite程序用來設(shè)置與一個(gè)電影相關(guān)的網(wǎng)站。它有兩個(gè)參數(shù):第一個(gè)是電影的片名,第二個(gè)是電影的網(wǎng)站URL。初始化這個(gè)程序?qū)嵗,executeTransaction()方法被調(diào)用,而該方法中會(huì)調(diào)用本程序的execute()方法。

  行①調(diào)用getMovie()(在例1-12中定義)來取得指定片名的Movie對(duì)象,如果getMovie()返回null,程序會(huì)報(bào)告找不到該片名的電影,然后退出。否則,在行②中我們調(diào)用setWebSite()(在例1-1中定義),以便設(shè)置該Movie對(duì)象的webSite屬性為給出的參數(shù)值。當(dāng)executeTransaction()提交這個(gè)事務(wù)的時(shí)候,對(duì)Movie實(shí)例的修改會(huì)自動(dòng)被同步到數(shù)據(jù)庫中。

  例1-13 更改一個(gè)屬性

  package com.mediamania.prototype;
  import com.mediamania.MediaManiaApp;
  public class UpdateWebSite extends MediaManiaApp {
   private String movieTitle;
   private String newWebSite;
   public static void main(String[] args)) {
   String title = args[0];
   String website = args[1];
   UpdateWebSite update = new UpdateWebSite(title, website);
   update.executeTransaction();
   }
   public UpdateWebSite(String title, String site) {
   movieTitle = title;
   newWebSite = site;
   }
   public void execute() {
  ① Movie movie = PrototypeQueries.getMovie(pm, movieTitle);
   if (movie == null) {
   System.err.print("Could not access movie with title of ");
   System.err.println(movieTitle);
   return;
   }
 、 movie.setWebSite(newWebSite);
   }
  }
  在例1-13中,你可以看到,程序并不需要調(diào)用任何JDO接口來更改Movie對(duì)象的屬性,這個(gè)程序訪問了一個(gè)實(shí)例然后調(diào)用一個(gè)方法更改它的網(wǎng)站屬性,這個(gè)方法采用Java的標(biāo)準(zhǔn)語法來更改對(duì)應(yīng)的屬性。而在提交之前無需任何額外的編碼來將更新同步到數(shù)據(jù)庫,JDO環(huán)境會(huì)自動(dòng)地同步變化。本程序執(zhí)行了對(duì)已存儲(chǔ)的實(shí)例的操作,而不需要直接導(dǎo)入或者使用任何JDO接口。

  現(xiàn)在我們看看一個(gè)大一些的程序,名為LoadRoles,來展示JDO的一些特性。LoadRoles,見例1-14,負(fù)責(zé)調(diào)入一部電影的角色以及扮演這些角色的演員的信息。LoadRoles被傳入一個(gè)單獨(dú)的參數(shù),用于指明一個(gè)文件名,然后程序的構(gòu)造器中初始化一個(gè)BufferedReader來讀取這個(gè)文件。它讀取文件的文本,每行一個(gè)角色,按以下的格式:

  movie title;actor's name;role name

  通常某部電影的所有角色被組合放到本文件中的相鄰的位置;LoadRoles采用一些小的優(yōu)化來決定當(dāng)前正處理的角色是否與前一個(gè)角色同屬一部電影。

  例1-14 實(shí)例更改和按可達(dá)性存儲(chǔ)(persistence-by-reachability)

  package com.mediamania.prototype;
  import java.io.FileReader;
  import java.io.BufferedReader;
  import java.util.StringTokenizer;
  import com.mediamania.MediaManiaApp;
  public class LoadRoles extends MediaManiaApp {
   private BufferedReader reader;
   public static void main(String[] args)) {
   LoadRoles loadRoles = new LoadRoles(args[0]);
   loadRoles.executeTransaction();
   }
   public LoadRoles(String filename) {
   try {
   FileReader fr = new FileReader(filename);
   reader = new BufferedReader(fr);
   } catch (java.io.IOException e) {
   System.err.print("Unable to open input file ");
   System.err.println(filename);
   System.exit( -1);
   }
   }
   public void execute() {
   String lastTitle = "";
   Movie movie = null;
   try {
   while (reader.ready()) {
   String line = reader.readLine();
   StringTokenizer tokenizer = new StringTokenizer(line, ";");
   String title = tokenizer.nextToken();
   String actorName = tokenizer.nextToken();
   String roleName = tokenizer.nextToken();
   if (!title.equals(lastTitle)) {
  ① movie = PrototypeQueries.getMovie(pm, title);
   if (movie == null) {
   System.err.print("Movie title not found:");
   System.err.println(title);
   continue;
   }
   lastTitle = title;
   }
 、 Actor actor = PrototypeQueries.getActor(pm, actorName);
   if (actor == null) {
  ③ actor = new Actor(actorName);
 、 pm.makePersistent(actor);
   }
 、 Role role = new Role(roleName, actor, movie);
   }
   } catch (java.io.IOException e) {
   System.err.println("Exception reading input file");
   System.err.println(e);
   return;
   }
   }
  }
  
  其中的execute()方法讀取文件中的每一行信息。首先,它檢查該行的電影片名是否與前一行一樣,如果不是,行①調(diào)用getMovie()來根據(jù)片名獲取該電影,如果該片名的電影在數(shù)據(jù)庫中不存在,則程序輸出一個(gè)錯(cuò)誤信息,并跳過這行信息。行②試著訪問指定姓名的演員,如果數(shù)據(jù)庫中找不到該姓名的演員,則一個(gè)新的演員會(huì)被創(chuàng)建,在行③中設(shè)置其姓名,然后在行④中保存之。

  程序中至此我們已經(jīng)讀取了文件信息并在數(shù)據(jù)庫中按文件中給出的名稱查找了相關(guān)的實(shí)例。而真正完成任務(wù)的行是行⑤,該行創(chuàng)建一個(gè)新的角色實(shí)例,這個(gè)角色的構(gòu)造器在例1-3中已經(jīng)定義;在此我們重復(fù)一下以便更詳細(xì)地看看:


  public Role(String name, Actor actor, Movie movie) {
 、 this.name = name;
 、 this.actor = actor;
 、 this.movie = movie;
  ④ actor.addRole(this);
 、 movie.addRole(this);
  }
  
  行①初始化本角色的名稱,行②建立一個(gè)到相關(guān)的演員對(duì)象的引用,行③建立一個(gè)到相應(yīng)的電影實(shí)例的引用。Actor與Role之間的關(guān)系和Movie與Role之間的關(guān)系都是雙向的,因此關(guān)系的另一端也需要作相應(yīng)更新,行④中我們調(diào)用演員的addRole()方法,它將本角色加入到該演員對(duì)象的roles集合中;類似地,行⑤中我們調(diào)用電影對(duì)象的addRole()方法將本角色加入到電影對(duì)象的cast(角色表)集合中。在Actor.roles中和Movie.cast中加入當(dāng)前角色作為一個(gè)元素將引起被actor和movie引用到的對(duì)象發(fā)生變化。

   Role構(gòu)造器展示了你可以通過簡單地建立一個(gè)引用來建立到另一個(gè)實(shí)例的關(guān)系,也可以將一個(gè)或多個(gè)實(shí)例加入到引用的集合中來建立到另一實(shí)例的關(guān)系。這個(gè)過程是Java中的對(duì)象關(guān)系的體現(xiàn),在JDO中也得到直接支持。當(dāng)事務(wù)提交后,內(nèi)存中建立的關(guān)系將被同步到數(shù)據(jù)庫中。

   Role構(gòu)造器返回后,load()方法處理文件中的下一行。這個(gè)while循環(huán)在讀完文件中的所有行后結(jié)束。

  你可能已經(jīng)注意到我們從沒有對(duì)Role實(shí)例調(diào)用makePersistent()方法,而在提交時(shí),Role實(shí)例也將被保存到數(shù)據(jù)庫,因?yàn)镴DO支持"可達(dá)性存儲(chǔ)(persistence-by-reachability)"?蛇_(dá)性存儲(chǔ)使得一個(gè)可存儲(chǔ)類的任何未存儲(chǔ)的實(shí)例在提交時(shí)被保存起來,只要從一個(gè)已經(jīng)被保存的實(shí)例可以直接或間接地到達(dá)這個(gè)實(shí)例。實(shí)例的可達(dá)性基于直接的引用或者集合型的引用。一個(gè)實(shí)例的所有可達(dá)實(shí)例集合所形成的對(duì)象樹稱作該實(shí)例的"相關(guān)實(shí)例完全閉包(complete closure)"。可達(dá)性規(guī)則被傳遞性地應(yīng)用在所有可存儲(chǔ)實(shí)例的在內(nèi)存中的所有引用中,從而使得整個(gè)完全閉包成為可存儲(chǔ)的。

  從其它存儲(chǔ)實(shí)例中去掉所有的對(duì)某個(gè)存儲(chǔ)實(shí)例的引用并不會(huì)自動(dòng)地將被去掉的實(shí)例刪除,你需要顯式地刪除這個(gè)實(shí)例,這將是我們下一小節(jié)將要涉及的。如果你在一個(gè)事務(wù)中建立了一個(gè)存儲(chǔ)實(shí)例到非存儲(chǔ)實(shí)例的引用,但接著又修改了引用關(guān)系使非存儲(chǔ)實(shí)例不被引用,那么在提交的時(shí)候,這個(gè)非存儲(chǔ)實(shí)例仍保持非存儲(chǔ)狀態(tài),不會(huì)被保存到數(shù)據(jù)庫。

  存儲(chǔ)可達(dá)性讓你可以寫一大堆代碼而不需要調(diào)用任何JDO的接口來保存實(shí)例,因此你的代碼可以集中在如何在內(nèi)存中建立實(shí)例間的關(guān)系,而JDO產(chǎn)品會(huì)將你在內(nèi)存中通過關(guān)系建立的非存儲(chǔ)實(shí)例保存到數(shù)據(jù)庫。你的程序可以在內(nèi)存中建立相當(dāng)復(fù)雜的對(duì)象體系圖然后從一個(gè)已存儲(chǔ)實(shí)例建立一個(gè)到這個(gè)圖的引用來完成這些新對(duì)象的保存。

   刪除實(shí)例

  現(xiàn)在我們來看看一個(gè)從數(shù)據(jù)庫中刪除一些實(shí)例的程序。在例1-15中,DeleteMovie程序用來刪除一個(gè)Movie實(shí)例。要?jiǎng)h除的電影的片名作為參數(shù)給出。行①試著訪問這個(gè)電影實(shí)例,如果該片名的電影不存在,程序報(bào)告錯(cuò)誤并退出。行⑥中我們調(diào)用deletePersistent()方法來刪除該Movie實(shí)例自身。

  例1-15 從數(shù)據(jù)庫刪除一個(gè)Movie實(shí)例
   package com.mediamania.prototype;
  import java.util.Collection;
  import java.util.Set;
  import java.util.Iterator;
  import javax.jdo.PersistenceManager;
  import com.mediamania.MediaManiaApp;
  public class DeleteMovie extends MediaManiaApp {
   private String movieTitle;
   public static void main(String[] args)) {
   String title = args[0];
   DeleteMovie deleteMovie = new DeleteMovie(title);
   deleteMovie.executeTransaction();
   }
   public DeleteMovie(String title) {
   movieTitle = title;
   }
   public void execute() {
 、 Movie movie = PrototypeQueries.getMovie(pm, movieTitle);
   if (movie == null) {
   System.err.print("Could not access movie with title of ");
   System.err.println(movieTitle);
   return;
   }
 、 Set cast = movie.getCast();
   Iterator iter = cast.iterator();
   while (iter.hasNext()) {
   Role role = (Role) iter.next();
 、 Actor actor = role.getActor();
 、 actor.removeRole(role);
   }
 、 pm.deletePersistentAll(cast);
  ⑥ pm.deletePersistent(movie);
   }
  }
  

  然后,我們也需要?jiǎng)h除該電影的所有角色實(shí)例,此外,由于演員實(shí)例中含有到角色實(shí)例的引用,我們也需要?jiǎng)h除這些引用。行②中我們?nèi)〉门cMovie實(shí)例相關(guān)的Role實(shí)例集合,然后遍歷每個(gè)角色,在行③中取得它關(guān)聯(lián)的演員,因?yàn)槲覀円獎(jiǎng)h除這個(gè)角色,所以在行④中我們?nèi)サ粞輪T對(duì)該角色的引用。行⑤中我們調(diào)用了deletePersistentAll()來刪除該電影的角色表中的所有角色實(shí)例。當(dāng)事務(wù)提交時(shí),電影實(shí)例和相關(guān)的角色實(shí)例被從數(shù)據(jù)庫中刪除,而與該電影相關(guān)的所有演員也得到相應(yīng)更新,從而不再含有對(duì)這些已刪除的角色的引用。

  你必須調(diào)用這些deletePersistent()方法來顯式地刪除數(shù)據(jù)庫中的實(shí)例,它們并不是makePersistent()的反向方法因?yàn)閙akePersistent()采用了可達(dá)性存儲(chǔ)的規(guī)則。進(jìn)一步,JDO的數(shù)據(jù)庫沒有Java中的垃圾回收機(jī)制可以讓一個(gè)實(shí)例在不被數(shù)據(jù)庫中其它實(shí)例引用的時(shí)候被自動(dòng)刪除。實(shí)現(xiàn)等價(jià)的垃圾回收機(jī)制是一個(gè)非常復(fù)雜的手續(xù),而且這樣的系統(tǒng)常常變得性能低下。

  小結(jié)

  你已經(jīng)看到,一個(gè)具有一定規(guī)模的應(yīng)用程序可以完成獨(dú)立于JDO來編寫,采用傳統(tǒng)的Java建模、語法和編程技巧。你可以基于Java的對(duì)象模型來定義應(yīng)用程序中要保存的信息。當(dāng)你采用類擴(kuò)展或查詢到訪問數(shù)據(jù)庫中的實(shí)例的時(shí)候,你的代碼看上去與其它的訪問內(nèi)存中的實(shí)例的Java程序沒什么區(qū)別。你不需要學(xué)習(xí)其它的數(shù)據(jù)模型或類似SQL的訪問語言,你不需要給出從內(nèi)存中的對(duì)象到數(shù)據(jù)庫中的鏡像數(shù)據(jù)之間的映射方法。你可以充分地利用Java中的面向?qū)ο蟮奶匦远皇苋魏蜗拗疲ㄗg者注:限制實(shí)際上還是有的,只不過影響不大),這包括使用繼承和多態(tài)(polymorphism),而這些在JDBC或EJB體系中是不可能的;比起這些其它的競爭技術(shù)來,你能用對(duì)象模型和很少量的代碼開發(fā)應(yīng)用程序。簡單、平常的Java對(duì)象可以用一種透明的方式保存到數(shù)據(jù)庫或在數(shù)據(jù)庫中訪問。JDO提供了一個(gè)非常容易上手而高效的環(huán)境來編寫需要保存數(shù)據(jù)的Java應(yīng)用程序。