You can make most of your classes persistent in a JDO environment. JDO has the ability to make plain ordinary Java objects (POJOs) persistent. This includes classes that represent the entities in your application domain, utility classes that model other data, and abstractions you need to support your application's functionality. Your classes can also use all of Java's class and field modifiers, including: private, public, protected, static, transient, abstract, final, synchronized, and volatile. In some cases, as we will explore later in this chapter, some of these modifiers cannot be used with persistent fields.
The persistent state of a persistent class is represented entirely by the values of its Java fields. If you have a class that has some state that needs to be preserved and it depends on inaccessible or remote objects (e.g., it extends java.net.SocketImpl or uses Java Native Interface (JNI)), you cannot make the class persistent. You also cannot have a persistent nonstatic inner class, because the state of the inner class instance depends on the state of its enclosing instance.
With a few exceptions, system-defined classes (those defined in java.lang, java.io, java.net, etc.) cannot be persistent. They are also not allowed to be the type of a persistent field. This includes classes such as System, Thread, Socket, and File. We list the system classes that are supported in Table 4-2 later in this chapter. You may be using an implementation that supports additional system-defined classes, especially those for modeling state information. Relying on support for these additional types will make your software dependent on that implementation.
As discussed in Chapter 1, each persistent class needs to have a no-arg constructor. If your class does not define any constructors, the Java compiler generates a no-arg constructor automatically (called the default constructor). But if you do define one or more constructors with arguments in a persistent class, then you must also define a no-arg constructor manually.
When your application first accesses a persistent instance, the JDO implementation needs to construct an instance, so it calls the no-arg constructor. The availability of a no-arg constructor is the only requirement JDO imposes on your persistent classes. Some JDO enhancers can generate this no-arg constructor for you if it does not already exist, but they are not required to do so.
You may not want other classes in your application calling the no-arg constructor. If this is the case, you can declare it to be private. Or, if the class will have subclasses, declare it to be protected so that the subclass constructors can call it.
Every class that you want to be persistent must be declared in a JDO metadata file. This file cannot include any system classes. Any class that is not declared in a metadata file is a transient class, except for the system classes that all implementations support. You typically place additional persistence-related information that is not expressable in Java in the metadata file. This metadata is used when a class is enhanced and also at runtime.
JDO metadata is stored in XML format. An XML Document Type Definition (DTD) defines the elements in a JDO metadata file. The JDO DTD is provided in Appendix B. It should be identical across all implementations.
You can place the metadata for your application's classes in one or more XML files. A few rules exist for the naming and directory placement of metadata files to assure portability among implementations. For portability, metadata files should be available via resources loaded by the same class loader as the persistent classes.
If you have a metadata file that contains information for a package or multiple packages, then the name of the XML file should be package.jdo. (Here we literally mean the word "package," not the name of an actual Java package.) The package.jdo file can be placed in one of the following directories:
In this case, package.jdo can contain metadata for any class in your application.
Files like package.jdo should be placed in this directory when deploying a JDO application in a web container.
The package.jdo file is not in any subdirectory of the classpath.
The package.jdo file is placed in the subdirectory that corresponds to the package defined in the metadata. Thus, if package.jdo contains the metadata for the com.mediamania.content package, it would placed in the com/mediamania/content directory.
If you have a metadata file that only contains information for a single class named classname, then its filename should be classname.jdo and it should reside in the same directory as the class file, based on the package of the class.
When the JDO implementation needs metadata for a class and the metadata has not been loaded yet, the metadata is searched in the following order:
where <package> represents the directory corresponding to the package of the class and <class> represents the name of the class.
A search for the metadata for the Customer class in the com.mediamania.store package is performed in the following order:
If no metadata is found for the Customer class in any of these locations, it is considered a transient class.
Once the metadata for a class has been loaded, it is not replaced. Metadata contained in a file higher in the search order is used instead of metadata lower in the search order. This search order is optimized so that implementations can cache metadata as soon as it is encountered, reducing the number of file accesses that are needed to load the metadata.
Metadata that is not in its natural location may override metadata that is in its natural location. For example, when the JDO implementation searches for the metadata for com.mediamania.content.Movie, it may find the metadata for the com.mediamania.store.Rental class in the com/mediamania/package.jdo file. In this case, a subsequent search for the metadata for com.mediamania.store.Rental will use the metadata that has already been cached, instead of looking in com/mediamania/store/package.jdo or com/mediamania/store/Rental.jdo.
These rules for the name and location of the metadata files apply both during enhancement and at runtime. From now on, the term "metadata" refers to the aggregate of all the JDO metadata for all packages and classes, regardless of their physical packaging in multiple files and directory placement.
The jdo element is the highest-level XML element in the metadata hierarchy. It does not have any attributes of its own. It contains one or more nested package elements. A package element is used to represent a specific Java package. It has a single required attribute, called name, that contains the completely qualified name of the Java package.
Within a package element, you can nest one or more class elements. A class element identifies a specific Java class in the enclosing package as persistent. The class element's only required attribute is name, which is given the name of the class. You should only list classes in the metadata that you want to be persistent.
The class element has the following additional optional attributes:
The identity-type attribute indicates which type of identity should be used with the class. It defaults to datastore identity, which does not require any additional effort from you. The objectid-class attribute identifies a class defined by the application to serve as the application identity of the class. Chapter 10 covers the various forms of identity in detail; until then, we will use datastore identity in all of our examples. The requires-extent attribute indicates whether an extent is maintained for the class. Extents are covered in Chapter 8. The persistence-capable-superclass attribute identifies the closest superclass in the inheritance hierarchy that is persistent, if there is one.
The extension element specifies vendor-specific metadata extensions in a uniform manner. All JDO metadata elements can have nested extension elements. The required vendor-name attribute associates the extension with a specific vendor. Each vendor uses a unique name to identify metadata extensions for their implementation. The vendor name "JDORI" is reserved for use with the JDO reference implementation. A JDO implementation ignores any extension elements that have a vendor-name value that does not correspond to their implementation. The extension element also has optional key and value attributes. A key may or may not have an associated value. The vendor chooses values for these attributes that they recognize and interpret. Consult your documentation to see what metadata extensions are provided.
The following illustrates the hierarchical nesting of metadata elements:
jdo package class field collection extension extension field map extension field array extension extension extension extension extension
One or more extension elements can be nested within each of these elements (including extension itself) to provide vendor-specific information. The field metadata elements (field, collection, map, and array) are covered later in this chapter.
Each class in an inheritance hierarchy can be transient or persistent, independent of the persistence of other classes in the hierarchy. Thus, a class can be persistent, even if its superclass is not. This allows you have a persistent class that extends a transient class that was not designed to be persistent. Likewise, a subclass of a persistent class may be transient or persistent.
If a persistent class has one or more persistent superclasses, the class element's persistence-capable-superclass attribute must identify the most immediate persistent superclass. If the superclass is in a different package, it must be specified with its fully qualified name. If the superclass is in the same package, you can omit the package qualifier. You may wonder why you need to specify this in the metadata. After all, the Java class declarations specify the branch of superclasses from a class up to Object in an inheritance hierarchy, and your metadata identifies which of these classes are persistent. But the metadata for a superclass may be specified in a different metadata file. JDO is designed such that the enhancer can enhance a class in a stateless fashion, independent from other classes. The order in which classes are enhanced is irrelevant, and a class can be enhanced without the presence of any other classes. This greatly supports the simplicity of enhancer design, ease of use, integration with classloaders, andlast, but not leasteasy reproducability of errors.
To illustrate these concepts, the UML diagram in Figure 4-1 describes two inheritance hierarchies. We use the stereotyping facility in UML to indicate whether a class is persistent or transient. In practice, you are not likely to have an inheritance hierarchy with such a complicated mix of persistent and transient classes. In many cases, the classes in an inheritance hierarchy are either all transient or all persistent. But JDO provides you with the flexibility to choose whether each class in an inheritance hierarchy is transient or persistent, as we have demonstrated here.
The following metadata identifies the persistent superclass for each persistent class shown in Figure 4-1. This metadata is placed in the com/mediamania/inheritexample/package.jdo file.
<?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.inheritexample" > <class name="A" /> <class name="C" persistence-capable-superclass="A"/> <class name="E" persistence-capable-superclass="A"/> <class name="G" persistence-capable-superclass="C"/> <class name="H" persistence-capable-superclass="A"/> <class name="K" /> <class name="M" /> <class name="O" persistence-capable-superclass="K"/> </package> </jdo>
Let's examine the object model we use in most of the examples throughout this book. Media Mania, Inc. provides a system in their stores that contains information about the various forms of media that customers can rent or purchase. In Chapter 1 we created a prototype application contained in com.mediamania.prototype. Now, we replace this prototype with two new packages: com.mediamania.content and com.mediamania.store.
The com.mediamania.content Java package contains classes that represent generic media content information. The content handled by the stores includes movies and games. The Movie and Game classes extend an abstract base class called MediaContent. The Studio class contains information about the studio that produced the game or movie. Figure 4-2 illustrates the relationships among these classes.
Each person involved in a movie, as either the director or an actor, is represented by an instance of MediaPerson. Figure 4-3 illustrates the relationships among Movie and MediaPerson instances.
A Movie instance has one or more Role instances representing the cast of the movie. It also has a reference to the MediaPerson for the director of the movie. We assume a movie has a single director (though in real life this is not always the case). The Role class references its Movie and a MediaPerson who served as the actor for the particular role. Given a specific MediaPerson instance, it is possible to access all the movies they directed and all the roles they have played in a movie. This model also allows for an actor who has played multiple roles in the same movie.
In addition to the media content information, each store tracks the rental and purchase activities of its customers. The com.mediamania.store package contains the classes representing store-specific information. Figure 4-4 illustrates the relationships among these classes.
Each customer that has rented or purchased some media content at the store is represented by an instance of the Customer class. An Address instance contains address information for the customer. The store tracks two kinds of transactions: rentals and purchases. These are represented by Rental and Purchase classes that extend a Transaction base class. The store tracks the current items the customer has out for rent and also keeps a history of all the customer's transactions.
A MediaItem instance represents a particular format of a given MediaContent item. For example, a Movie can exist in VHS and DVD formats and a Game may be supported in formats for the Playstation, Playstation 2, Xbox, and Nintendo GameCube. The stock of media items is designated as items to be sold or rented. A RentalItem instance exists for each individual item that can be rented to a customer. The items in stock that are currently available for rent are represented by RentalItem instances that have a null value for their currentRental field. The model does not track the individual items that are sold, but the MediaItem class tracks how many items for purchase are in stock and how many have been sold year-to-date. Each Purchase instance contains a reference to the specific MediaItem that the customer bought.
The store has different rental policies and prices, based on the popularity of an item and how recently it became available. A RentalCode instance maintains information about a particular rental policy. Each MediaItem instance is associated with a particular RentalCode, which may change over time.
A Rental instance represents a customer's rental of a particular media item; it references the specific RentalItem rented. This is necessary so the store can track which item has been rented and update the customer account when it is returned, taking into account any late fees that may be due. The RentalCode associated with the MediaItem at the time of rental is associated with the Rental instance. This is necessary because the RentalCode for a MediaItem will change occasionally.
Appendix E provides all the classes for the model. The following metadata specifies the packages and persistent classes for the object model. Since it contains metadata information for the com.mediamania.content and com.mediamania.store packages, we place the metadata in a file named com/mediamania/package.jdo, based on their common base package name.
<?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.content" > <class name="Studio" > </class> <class name="MediaContent" /> <class name="Movie" persistence-capable-superclass="MediaContent"> </class> <class name="MediaPerson" > </class> <class name="Game" /> <class name="Role" /> </package> <package name="com.mediamania.store" > <class name="MediaItem" > </class> <class name="RentalItem"/> <class name="Customer" > </class> <class name="Address" /> <class name="Transaction" /> <class name="Purchase" persistence-capable-superclass="Transaction"/> <class name="Rental" persistence-capable-superclass="Transaction"/> <class name="RentalCode" /> </package> </jdo>
The metadata lists each persistent class in the content and store packages. If an inheritance relationship exists, the metadata specifies the persistent superclass. Later in this chapter, we will add more information that provides information about the fields and relationships.