六月婷婷综合激情-六月婷婷综合-六月婷婷在线观看-六月婷婷在线-亚洲黄色在线网站-亚洲黄色在线观看网站

明輝手游網中心:是一個免費提供流行視頻軟件教程、在線學習分享的學習平臺!

Java數據對象技術JDO

[摘要]作為異軍突起的新型語言,Java定義了一個標準的運行環境,用戶定義的類在其中得到執行。這些用戶自定義類的實例代表了真實環境中的數據,包括儲存在數據庫、文件或某些大型事務處理系統中的數據,而小型系統通常也需要一種在本地負責控制數據存儲的機制。    由于數據訪問技術在不同的數據源類型中是不一樣的,因...

  作為異軍突起的新型語言,Java定義了一個標準的運行環境,用戶定義的類在其中得到執行。這些用戶自定義類的實例代表了真實環境中的數據,包括儲存在數據庫、文件或某些大型事務處理系統中的數據,而小型系統通常也需要一種在本地負責控制數據存儲的機制。

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

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

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

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

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

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

  定義數據對象模型

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

  我們會將這些數據類以及操縱這些數據類實例的的程序放到com.mecdiamania.prototype包中。

  需要存儲的類

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

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

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

   "genres"屬性是一個String型的,內容是該電影所屬的電影風格(動作、愛情、詭異等等)。一個Set接口用來表示該電影的演員表中的角色集合。"addRole()"方法將元素加入到演員表中,而"getCast()"方法返回一個不可以更改的集合,該集合中包含演員表。這些方法并不是JDO規定的,只是為了方便應用編程而編寫的。"parseReleaseDate()"方法和"formatReleaseDate()"方法用于將電影的發行日期標準化(格式化)。為了保持代碼的簡單,如果parseReleaseDate()的參數格式不對,將會返回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對一個需要存儲的類強加了一個要求:一個無參數的構造器。如果你在類代碼中不定義任何構造器,編譯器會自動產生一個無參數的構造器;而如果你定義了帶參構造器,你就必須再定義一個無參構造器,可以將其聲明為private以禁止外部訪問。如果你不定義這個無參構造器,一些JDO產品會自動為你產生一個,但這只是具體的JDO產品提供的功能,是不可移植的。

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

  例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類的源碼。這個類代表了Movie類和Actor類之間的關系,并且包含某個演員在某部電影中扮演的具體角色的名字。其構造器初始化了對Movie和Actor對象的引用,并且通過調用處于關系的另一端的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;
   }
  }

  至此,我們已經了解了在數據庫中有實例存在的每個類的源碼。這些類并不需要導入或使用任何JDO相關的具體類。進一步,除了無參的構造器,無須任何數據或方法來標明這些類為可存儲的。用于訪問或更新屬性數據并維護實例間的關系的代碼與大多數Java應用中的標準代碼是一模一樣的。

   將類聲明為可存儲的

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

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

  例1-4 …/prototype/package.jdo文件中的JDO元數據

   <?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"文件提供了對JDO元數據文件中用到的元素的定義。這個文檔類型定義(DTD)是由JDO規范所規定的,必須由一個JDO產品附帶提供。該文件也可以在http://java.sun.com/dtd下載。你也可以將"DOCTYPE"行中的內容改為指向你本地文件系統中的一個副本文件。

  元數據文件可以包含與一個或多個含有可存儲類的包的關于一些存儲細節方面的信息。每個包由一個"package"元素進行定義,該元素具有一個"name"屬性來表示該包的名稱。第②行給出了我們的com.mediamania.prototype包的對應包元素。在該包元素內,是各個該包中的類元素。(如第③行就是Movie類的描述元素)。這個文件中可以順序寫入多個包元素,它們不能互相嵌套。

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

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

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

  項目編譯環境

  在本節中,我們將查看一下用于編譯和運行我們的JDO應用程序的開發環境。這包括項目的文件目錄結構,編譯所需要的相關的jar文件,以及對可存儲的類進行增強(Enhance,參見術語表)的語法(我們將在本節的后面部分詳細說明類的增強這個概念)。這個環境的建立一般與你所具體使用的JDO產品有關,所以你實際的項目開發環境及相關目錄結構可能會稍有不同。

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

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

   JDO參考產品需要的jar文件

  當你采用JDO參考產品后,你需要在開發過程中將下列jar文件放到你的CLASSPATH中。在運行時,所有這些jar文件也必須處于你的CLASSPATH中。

  jdo.jar

   JDO規范定義的標準幾個的接口和類。

  jdori.jar

   Sun公司的參考產品的jar文件

  btree.jar

   JDO參考產品所用到的軟件,用于管理文件中存儲的數據。JDO參考產品采用一個文件來保存數據對象。

  jta.jar

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

  antlr.jar

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

  xerces.jar

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

  前三個文件是包含在JDO參考產品中的;后三個文件可以從各自的網站上下載。

  參考產品還包含一個jar文件:jdo-enhancer.jar,其中包括參考增強器。其中的所有類在jdori.jar文件中也有。多數情況下,你會在開發環境和運行環境都使用jdori.jar,不需要jdori-enhancer.jar文件。jdori-enhancer.jar文件被單獨打包原因是這樣一來你可以獨立于具體使用的JDO產品而對類代碼進行增強。除參考產品之外,一些其它的產品也會將這個jar文件與產品一起發布。

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

  項目目錄結構

  對于Media Mania應用開發環境來說,你需要采用下面的目錄結構,這個項目必須有一個根目錄,存在于系統的文件體系的某個地方。下面這些目錄都是以這個根目錄為基準的:

  src

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

  classes

   當Java源碼被編譯時,生成的.class文件置于這個目錄中

  enhanced

   這個目錄存放增強后的.class類代碼文件(由增強器所產生)

  database

   這個目錄存放JDO參考產品用于存儲數據的文件。

  盡管這樣的目錄結構并不是JDO規范所要求的,但你得理解它,這樣才能跟隨我們對Media Mania應用的描述。

  當你執行你的JDO應用時,Java運行環境必須調入增強版本的類文件,也就是處于enhanced目錄中的類文件。因此,在你的CLASSPATH中這個目錄必須處于classes目錄之前。作為一種可選方案,你也可以采用就地增強,用你的增強后的類文件直接替換未增強的文件。

   增強類代碼以便存儲

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

  前面已經提到,Sun公司提供的JDO參考產品中的增強器稱作參考增強器。而JDO產品廠商一般可能會提供自己的增強器;在命令行調用增強器的語法可能會與這里提到的有所不同。每個產品都會向你提供文檔以闡釋如果在該產品上對你的類進行增強。

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

  例1-5 對類進行增強

  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

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

  在例1-5中,我們的對象模型中的所有.class類文件被一起列出,但你也可以將每個類文件單獨增強。當這個增強命令執行時,它將增強后的新文件放到enhanced目錄下。

  創建數據庫連接和事務

  現在既然我們的類已經被增強了,它們的實例也就可以被儲存到數據庫中了。我們現在來看看應用中如果創建一個與數據庫的連接并在一個事務(Transaction)中執行一些操作。我們開始寫直接使用JDO接口的軟件代碼,所有的在應用中用到的JDO接口都定義在javax.jdo包中。

  JDO中有一個接口叫做PersistenceManager(存儲管理器,見術語表),它具有一個到數據庫的連接。一個PersistenceManager還有一個JDO中的Transaction(事務)接口的實例,用于控制一個事務的開始和結束。這個Transaction實例的獲取方式是調用PersistenceManager實例的currentTransaction()方法。

  獲取一個PersistenceManager

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

  static PersistenceManagerFactory getPersistenceManagerFactory(Properties props);

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

  例1-6 jdo.properties文件內容

  ① 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

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

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

  創建一個FOStore數據庫

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

  為完成數據庫的創建,我們還需要開始并結束一個事務。第⑤行中調用了PersistenceManager的currentTransaction()方法來訪問與該PersistenceManager相關聯的Transaction實例。第⑥行和第⑦行調用這個Transaction實例的begin()和commit()方法來開始和結束一個事務。當你執行這個程序時,在database目錄下就會生成一個FOStore數據庫,包括兩個文件:fostore.btd和fostore.btx.

  例1-7 創建一個FOStore數據庫

  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參考產品提供了這種程序化創建FODatastore數據庫的方式,而大多數數據庫都提供一個獨立于JDO的工具來創建數據庫。JDO并不規定一個與廠商無關的接口來創建數據庫。數據庫的創建一般都與具體使用的數據庫相關。本程序中顯示了在FOStore數據庫中是如何完成這一步的。

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

   對實例的操作

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

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

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

  當(子類中實現的)execute()方法返回后,我們會嘗試提交這個事務(行⑦),而如果有任何異常發生的話,我們會回滾(rollback)這個事務并將異常信息打印到系統錯誤輸出流中(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();
   }
   }
  }

  存儲實例

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

  例1-9 創建一個Movie實例并保存它

  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);
   }
  }

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

  例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);
   }
  }

  電影信息文件中的數據格式是:

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

  其中用于表示發行日期的數據格式由Movie類來控制,因此parseReleaseDate()被調用以根據發行日期數據產生一個Date實例。一部電影可以屬于多種風格,在數據行的尾部列出。

  訪問實例

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

   遍歷一個類擴展

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

  例1-11 遍歷Movie類的擴展

  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獲取一個Movie類的擴展,第二個參數表示是否希望包含Movie類的所有子類,false使得只有Movie類的實例被返回,即便是還有其它的Movie的子類的實例存在。盡管我們目前還沒有任何Movie的子類,以true作參數將保證我們將來可能加入的類似的Movie的子類的實例也被返回。Extent接口有一個iterator()方法,即我們在行②中調用以獲取一個Iterator遍歷器來逐個訪問這個類擴展中的每個實例。行③采用遍歷器來訪問Movie類的實例。程序在后面就可以針對Movie的實例進行操作來取得數據并打印出來。例如:行④中我們調用getGenres()來取得一部電影所屬的風格,行⑤中我們取得電影中的角色集合,在行⑥中取得每個角色并打印其名稱,行⑦中我們通過調用getActor()來瀏覽該角色的演員對象,這是我們在Role類中已經定義好的,我們打印了該演員的姓名。

   當這個程序結束對類擴展的遍歷之后,行⑧中關閉這個遍歷器來釋放執行這個遍歷時占用的相關資源。對一個擴展可以同時進行多個遍歷,這個close()方法關閉一個特定的遍歷器,而closeAll()方法可以關閉與一個類擴展相關聯的所有遍歷器。

  瀏覽對象模型

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

   訪問相關聯的實例所需要的語法與在內存中對關聯對象的標準瀏覽方式是完全一樣的。在行③和行⑦之間程序并不需要調用任何JDO的接口,它僅僅是在對象中通過關系來遍歷(traverse)。相關的實例直到被程序直接訪問到時才會被從數據庫讀入并在內存中生成。對數據庫的訪問是透明的,實例即需即調。某些JDO產品還提供Java接口之外的機制讓你調節對該JDO產品的緩沖的訪問機制。你的Java程序獨立于這些優化之外,但可以從這些優化中獲得運行性能上的改善。

  在JDO環境中訪問相關的數據庫對象的方式與在非JDO的環境訪問臨時(transient)對象的方式是一樣的,因此你可以按照非JDO的方式編寫你的軟件,F有的沒有任何針對JDO或其它方面的存儲因素的考慮的軟件可以通過JDO來完成對數據庫中的實例對象的瀏覽。這個特點極大地推動了開發生產力,也允許現有的軟件可以快速、方便地集成到JDO環境中。

  執行查詢

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

  例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()方法。在行①中我們取到一個Actor類的擴展,行②中通過在PersistenceManager接口中定義的newQuery()方法創建了一個Query實例,這個查詢建立在這個類擴展和相應的過濾條件的基礎上。

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

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

  更改實例

  現在我們看看兩個更改數據庫中的實例的程序。當一個程序在一個事務中訪問一個數據庫中的實例時,它可以更改這個實例的一個或多個屬性值。而事務提交時,所有對這些實例的更改會被自動地全部同步到數據庫中去。

  例1-13中給出的UpdateWebSite程序用來設置與一個電影相關的網站。它有兩個參數:第一個是電影的片名,第二個是電影的網站URL。初始化這個程序實例后,executeTransaction()方法被調用,而該方法中會調用本程序的execute()方法。

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

  例1-13 更改一個屬性

  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中,你可以看到,程序并不需要調用任何JDO接口來更改Movie對象的屬性,這個程序訪問了一個實例然后調用一個方法更改它的網站屬性,這個方法采用Java的標準語法來更改對應的屬性。而在提交之前無需任何額外的編碼來將更新同步到數據庫,JDO環境會自動地同步變化。本程序執行了對已存儲的實例的操作,而不需要直接導入或者使用任何JDO接口。

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

  movie title;actor's name;role name

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

  例1-14 實例更改和按可達性存儲(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()方法讀取文件中的每一行信息。首先,它檢查該行的電影片名是否與前一行一樣,如果不是,行①調用getMovie()來根據片名獲取該電影,如果該片名的電影在數據庫中不存在,則程序輸出一個錯誤信息,并跳過這行信息。行②試著訪問指定姓名的演員,如果數據庫中找不到該姓名的演員,則一個新的演員會被創建,在行③中設置其姓名,然后在行④中保存之。

  程序中至此我們已經讀取了文件信息并在數據庫中按文件中給出的名稱查找了相關的實例。而真正完成任務的行是行⑤,該行創建一個新的角色實例,這個角色的構造器在例1-3中已經定義;在此我們重復一下以便更詳細地看看:


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

   Role構造器展示了你可以通過簡單地建立一個引用來建立到另一個實例的關系,也可以將一個或多個實例加入到引用的集合中來建立到另一實例的關系。這個過程是Java中的對象關系的體現,在JDO中也得到直接支持。當事務提交后,內存中建立的關系將被同步到數據庫中。

   Role構造器返回后,load()方法處理文件中的下一行。這個while循環在讀完文件中的所有行后結束。

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

  從其它存儲實例中去掉所有的對某個存儲實例的引用并不會自動地將被去掉的實例刪除,你需要顯式地刪除這個實例,這將是我們下一小節將要涉及的。如果你在一個事務中建立了一個存儲實例到非存儲實例的引用,但接著又修改了引用關系使非存儲實例不被引用,那么在提交的時候,這個非存儲實例仍保持非存儲狀態,不會被保存到數據庫。

  存儲可達性讓你可以寫一大堆代碼而不需要調用任何JDO的接口來保存實例,因此你的代碼可以集中在如何在內存中建立實例間的關系,而JDO產品會將你在內存中通過關系建立的非存儲實例保存到數據庫。你的程序可以在內存中建立相當復雜的對象體系圖然后從一個已存儲實例建立一個到這個圖的引用來完成這些新對象的保存。

   刪除實例

  現在我們來看看一個從數據庫中刪除一些實例的程序。在例1-15中,DeleteMovie程序用來刪除一個Movie實例。要刪除的電影的片名作為參數給出。行①試著訪問這個電影實例,如果該片名的電影不存在,程序報告錯誤并退出。行⑥中我們調用deletePersistent()方法來刪除該Movie實例自身。

  例1-15 從數據庫刪除一個Movie實例
   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);
   }
  }
  

  然后,我們也需要刪除該電影的所有角色實例,此外,由于演員實例中含有到角色實例的引用,我們也需要刪除這些引用。行②中我們取得與Movie實例相關的Role實例集合,然后遍歷每個角色,在行③中取得它關聯的演員,因為我們要刪除這個角色,所以在行④中我們去掉演員對該角色的引用。行⑤中我們調用了deletePersistentAll()來刪除該電影的角色表中的所有角色實例。當事務提交時,電影實例和相關的角色實例被從數據庫中刪除,而與該電影相關的所有演員也得到相應更新,從而不再含有對這些已刪除的角色的引用。

  你必須調用這些deletePersistent()方法來顯式地刪除數據庫中的實例,它們并不是makePersistent()的反向方法因為makePersistent()采用了可達性存儲的規則。進一步,JDO的數據庫沒有Java中的垃圾回收機制可以讓一個實例在不被數據庫中其它實例引用的時候被自動刪除。實現等價的垃圾回收機制是一個非常復雜的手續,而且這樣的系統常常變得性能低下。

  小結

  你已經看到,一個具有一定規模的應用程序可以完成獨立于JDO來編寫,采用傳統的Java建模、語法和編程技巧。你可以基于Java的對象模型來定義應用程序中要保存的信息。當你采用類擴展或查詢到訪問數據庫中的實例的時候,你的代碼看上去與其它的訪問內存中的實例的Java程序沒什么區別。你不需要學習其它的數據模型或類似SQL的訪問語言,你不需要給出從內存中的對象到數據庫中的鏡像數據之間的映射方法。你可以充分地利用Java中的面向對象的特性而不受任何限制(譯者注:限制實際上還是有的,只不過影響不大),這包括使用繼承和多態(polymorphism),而這些在JDBC或EJB體系中是不可能的;比起這些其它的競爭技術來,你能用對象模型和很少量的代碼開發應用程序。簡單、平常的Java對象可以用一種透明的方式保存到數據庫或在數據庫中訪問。JDO提供了一個非常容易上手而高效的環境來編寫需要保存數據的Java應用程序。




主站蜘蛛池模板: 青草资源| 特级黄一级播放 | 欧美亚洲激情视频 | 日韩精品在线免费观看 | 手机看片福利盒子久久青 | 伊人久久伊人 | 欧美一级爽快片淫片高清在线观看 | 一级免费黄色大片 | 欧美丝袜一区二区 | 日本三级香港三级人妇99 | 婷婷中文在线 | 丝袜捆绑调教视频免费区 | 四虎国产一区二区三区 | 色亚洲视频| 污视频在线看 | 日本亚洲中午字幕乱码 | 在线播放日本爽快片 | 性欧美大战久久久久久久久 | 香港全黄一级毛片在线播放 | 欧美又粗又长 | 午夜黄网| 亚洲主播在线 | 性高湖久久久久久久久aaaaa | 日韩无| 天天综合色天天综合 | 日本高清免费一本视频无需下载 | 日韩黄色免费 | 欧美视频在线不卡 | 日韩国产欧美精品综合二区 | 伊人网站在线观看 | 热久久久久久久 | 中文有码中文字幕免费视频 | 亚洲国产精品日韩专区avtube | 青青在线视频 | 神兵小将第一部 | 日韩 欧美 国产 亚洲 中文 | 欧美亚洲另类色国产综合 | 日本三级理论 | 一级做受视频免费是看美女 | 日日爱网站 | 亚洲高清视频免费 |