Defining a Persistent Object Model

Figure 1-1 is a Unified Modeling Language (UML) diagram of the classes and interrelationships in the Media Mania object model. A Movie instance represents a particular movie. Each actor who has played a role in at least one movie is represented by an instance of Actor. The Role class represents the specific roles an actor has played in a movie and thus represents a relationship between Movie and Actor that includes an attribute (the name of the role). Each movie has one or more roles. An actor may have played a role in more than one movie or may have played multiple roles in a single movie.

UML diagram of the Media Mania object model

Figure 1-1. UML diagram of the Media Mania object model

We will place these persistent classes and the application programs used to manage their instances in the Java com.mediamania.prototype package.

The Classes to Persist

We will make the Movie, Actor, and Role classes persistent, so their instances can be stored in a datastore. First we will examine the complete source code for each of these classes. An import statement is included for each class, so it is clear which package contains each class used in the example.

Example 1-1 provides the source code for the Movie class. JDO is defined in the javax.jdo package. Notice that the class does not require you to import any JDO-specific classes. Java references and collections defined in the java.util package are used to represent the relationships between our classes, which is the standard practice used by most Java applications.

The fields of the Movie class use standard Java types such as String, Date, and int. You can declare fields to be private; it is not necessary to define a public get and set method for each field. The Movie class includes some methods to get and set the private fields in the class, though those methods are used by other parts of the application and are not required by JDO. You can use encapsulation, providing only the methods that support the abstraction being modeled. The class also has static fields; these are not stored in the datastore.

The genres field is a String that contains the genres of the movie (action, romance, mystery, etc.). A Set interface is used to reference a set of Role instances, representing the movie’s cast. The addRole( ) method adds elements to the cast collection, and getCast( ) returns an unmodifiable Set containing the elements of the cast collection. These methods are not a JDO requirement, but they are implemented as convenience methods for the application. The parseReleaseDate( ) and formatReleaseDate( ) methods are used to standardize the format of the movie’s release date. To keep the code simple, a null is returned if the parseReleaseDate( ) parameter is in the wrong format.

Example 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 imposes one requirement to make a class persistent: a no-arg constructor. If you do not define any constructors in your class, the compiler generates a no-arg constructor. However, this constructor is not generated if you define any constructors with arguments; in this case, you need to provide a no-arg constructor. You can declare it to be private if you do not want your application code to use it. Some JDO implementations can generate one for you, but this is an implementation-specific, nonportable feature.

Example 1-2 provides the source for the Actor class. For our purposes, all actors have a unique name that identifies them. It can be a stage name that is distinct and different from the given name. Therefore, we represent the actor’s name by a single String. Each actor has played one or more roles, and the roles member models the Actor’s side of the relationship between Actor and Role. The comment on line [1] is used merely for documentation; it does not serve any functional purpose in JDO. The addRole( ) and removeRole( ) methods in lines [2] and [3] are provided so that the application can maintain the relationship from an Actor instance and its associated Role instances.

Example 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     [1]

    private Actor(  )
    { }
    public Actor(String name) {
        this.name = name;
        roles = new HashSet(  );
    }
    public String getName(  ) {
        return name;
    }
    public void addRole(Role role) {     [2]
        roles.add(role);
    }
    public void removeRole(Role role) {     [3]
        roles.remove(role);
    }
    public Set getRoles(  ) {
        return Collections.unmodifiableSet(roles);
    }
}

Finally, Example 1-3 provides the source for the Role class. This class models the relationship between a Movie and Actor and includes the specific name of the role played by the actor in the movie. The Role constructor initializes the references to Movie and Actor, and it also updates the other ends of its relationship by calling addRole( ), which we defined in the Movie and Actor classes.

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

We have now examined the complete source code for each class that will have instances in the datastore. These classes did not need to import and use any JDO-specific types. Furthermore, except for providing a no-arg constructor, no data or methods needed to be defined to make these classes persistent. The software used to access and modify fields and define and manage relationships among instances corresponds to the standard practice used in most Java applications.

Declaring Classes to Be Persistent

It is necessary to identify which classes should be persistent and specify any persistence-related information that is not expressible in Java. JDO uses a metadata file in XML format to specify this information.

You can define metadata on a class or package basis, in one or more XML files. The name of the metadata file for a single class is the name of the class, followed by a .jdo suffix. So, a metadata file for the Movie class would be named Movie.jdo and placed in the same directory as the Movie.class file. A metadata file for a Java package is contained in a file named package.jdo. A metadata file for a Java package can contain metadata for multiple classes and multiple subpackages. Example 1-4 provides the metadata for the Media Mania object model. The metadata is specified for the package and contained in a file named com/mediamania/prototype/package.jdo.

Example 1-4. JDO metadata in the file prototype/package.jdo

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE jdo PUBLIC     [1]
    "-//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" >     [2]
        <class name="Movie" >     [3]
            <field name="cast" >     [4]
                <collection      [5]
element-type="Role"/>
            </field>
        </class>
        <class name="Role" />     [6]
        <class name="Actor" >
            <field name="roles" >
                <collection 
element-type="Role"/>
            </field>
        </class>
    </package>
</jdo>

The jdo_1_0.dtd file specified on line [1] provides a description of the XML elements that can be used in a JDO metadata file. This document type definition (DTD) is standardized in JDO and should be provided with a JDO implementation. It is also available for download at http://java.sun.com/dtd. You can also alter the DOCTYPE to refer to a local copy in your filesystem.

The metadata file can contain persistence information for one or more packages that have persistent classes. Each package is defined with a package element, which includes the name of the Java package. Line [2] provides a package element for our com.mediamania.prototype package. Within the package element are nested class elements that identify a persistent class of the package (e.g., line [3] has the class element for the Movie class). The file can contain multiple package elements listed serially; they are not nested.

If information must be specified for a particular field of a class, a field element is nested within the class element, as shown on line [4]. For example, you could declare the element type for each collection in the model. This is not required, but it can result in a more efficient mapping. The Movie class has a collection named cast, and the Actor class has a collection named roles; both contain Role references. Line [5] specifies the element type for cast. In many cases, a default value for an attribute is assumed in the metadata that provides the most commonly needed value.

All of the fields that can be persistent are made persistent by default. Static and final fields cannot be made persistent. A field declared in Java to be transient is not persistent by default, but such a field can be declared as persistent in the metadata file. Chapter 4 describes this capability.

Chapter 4, Chapter 10, Chapter 12, and Chapter 13 cover other characteristics you can specify for classes and fields. For a simple class like Role, which does not have any collections, you can just list the class in the metadata as shown on line [6], if no other metadata attributes are necessary.

Get Java Data Objects now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.