您的位置:首页 > 编程语言 > Java开发

Java数据对象技术JDO初步概览(Java数据对象技术JDO)

2005-09-30 20:02 447 查看
作为异军突起的新型语言,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定义了一个标准的运行环境,用户定义的类在其中得到执行。这些用户自定义类的实例代表了真实环境中的数据,包括储存在数据库、文件或某些大型事务处理系统中的数据,而小型系统通常也需要一种在本地负责控制数据存储的机制。

 
 
由于数据访问技术在不同的数据源类型中是不一样的,因此对数据进行访问成了给程序开发人员的一种挑战,程序员需要对每一种类型的数据源使用特定的编程接口
(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的方式编写你的软件。现有的没有任何针对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应用程序。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: