3.9 JDO Object Model
So far, the examples have used a simple Java class to demonstrate basic concepts. JDO can, of course, be used with much more sophisticated classes than this. In addition to supporting fields of primitive Java types and system classes like String, JDO can handle object references, Java collections, and inheritance of both classes and interfaces.
This section outlines what can and can't be used when developing a persistence-capable class, including the following:
-
The basic field types that can be used within a persistence-capable class.
-
Using references to persistence-capable classes, non-persistence-capable classes, and interfaces.
-
Using standard Java collections.
-
Using arrays.
-
Support for inheritance.
-
The class and field modifiers that can be used.
-
What JDO doesn't support.
3.9.1 Basic types
A persistence-capable class can have fields whose types can be any primitive Java type, primitive wrapper type, or other supported system immutable and mutable classes. Table 3-1 provides a summary of these basic types.
If a persistence-capable class has fields of any of the above types, then by default these fields are persisted in the datastore (providing they are not declared as transient). If a field is a reference to a persistence-capable class, then by default these fields are persisted also. Fields of any other types are not persisted in the data-store by default and need explicit metadata to indicate that they should be persisted. The types of these fields might be a reference to the Java interface, a java.lang.Object, a non-supported system class/interface, or a non-persistence-capable class.
Table 3-1. Supported Basic Types
Primitive Types |
Wrapper Classes |
Supported System Interfaces |
Supported System Classes |
---|---|---|---|
boolean |
java.lang.Boolean¥ |
java.util.Collection§ |
java.lang.String[¥] |
byte |
java.lang.Byte[¥] |
java.util.List*[§] |
java.math.BigDecimal[¥] |
char |
java.lang.Character[¥] |
java.util.Map[*][§] |
java.math.BigInteger[¥] |
double |
java.lang.Double[¥] |
java.util.Set[§] |
java.util.Date |
int |
java.lang.Integer[¥] |
javax.jdo.PersistenceCapable |
java.util.Locale[¥] |
float |
java.lang.Float[¥] |
java.util.ArrayList[*][§] |
|
long |
java.lang.Long[¥] |
java.util.HashMap[*][§] |
|
short |
java.lang.Short[¥] |
java.util.HashSet[§] |
|
java.util.Hashtable[*][§] |
|||
java.util.LinkedList[*][§] |
|||
java.util.TreeMap[*][§] |
|||
java.util.TreeSet[*][§] |
|||
java.util.Vector[*][§] |
JDO defines both mandatory and optional features. A JDO implementation is said to be compliant if it supports all the mandatory features defined by the JDO specification. An implementation may support one or more optional features, in which case it must adhere to what is defined for the optional feature by the JDO specification if it is to be JDO compliant.
From a portability standpoint, an application should not rely on support of an optional feature. But should a particular feature be required, then at least the application is still portable across implementations that support the feature or set of features used. See Chapter 5 for more details on specific optional features.
3.9.2 References
A persistence-capable class can have fields that reference instances of other persistence-capable classes. The following code snippet shows a Book class that has an "author" field, which is a reference to an instance of Author:
public class Book { private String name; private Author author; public Book(String name, Author author) { this.name = name; this.author = author; } protected Book() {} public String getName() { return name; } public void setName(String name) { this.name = name; } public Author getAuthor() { return author; } public void setAuthor(Author author) { this.author = author; } }
The JDO metadata for this class in Book.jdo is as follows:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE jdo SYSTEM "jdo.dtd"> <jdo> <package name="com.corejdo.examples.model"> <class name="Book"/> </package> </jdo>
The following code snippet taken from CreateWithReachabilityExample.java shows persistence by reachability at work. Both an Author and a Book instance are created. The Book instance is added to the Author instance, and the Author instance is explicitly made persistent, so the Book instance is automatically made persistent because it is reachable from the Author instance:
tx.begin(); Author author = new Author("Keiron McCammon"); Book book = new Book("Core Java Data Objects", "0-13-140731-7"); author.addBook(book); pm.makePersistent(author); tx.commit();
As well as supporting references to persistence-capable classes, JDO also supports references to Java interfaces and even java.lang.Object. Because fields of these types are not persistent by default, additional metadata is required to denote that the field should be persistent. Any referenced instances should still be instances of a persistence-capable class. (Support for references to non-persistence-capable classes is a JDO optional feature.)
The following code snippet shows a revised Book class that has an "author" field, which now is of type java.lang.Object instead of Author:
public class Book { private String name; private Object author; // Uses Object rather than Author /* Rest of code not shown */ }
The major difference here compared to the previous example is the metadata in Book.jdo:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE jdo SYSTEM "jdo.dtd"> <jdo> <package name="com.corejdo.examples.model"> <class name="Book"> <field name="author" persistence-modifier="persistent"/> </class> </package> </jdo>
Because the field's type is java.lang.Object, it is not persistent by default. It must be explicitly denoted as persistent in the metadata for the class. The same would be true if it were a reference to an interface.
A JDO implementation may optionally support references to instances that do not belong to a persistence-capable class. If not supported, a Java class cast exception would be thrown at the time of assignment. (An application intended to run against multiple JDO implementations should not rely on support for persisting non-persistence-capable classes.)
If references to non-persistence-capable classes are supported, then these instances exist in the datastore only as part of the referencing persistent object. It is the responsibility of the application to inform the JDO implementation when an instance of a non-persistence-capable class is modified because the JDO implementation is unable to automatically detect when a field of a non-persistence-capable class is changed.
The JDO implementation can't detect when an instance of a non-persistence-capable class is modified because the class doesn't adhere to the persistence-capable programming style. A persistence-capable class automatically informs the JDO implementation before a field is changed (this is what the byte code enhancement process takes care of), but a non-persistence-capable class does not.
If the JDO implementation is not explicitly notified of changes to non-persistence-capable classes, then the modifications do not get written to the datastore on commit. The easiest way to notify a JDO implementation that a non-persistence-capable instance has been modified is to use the makeDirty() method on JDOHelper:
static void makeDirty(Object pc, String field)
The first argument is the persistent object that references the non-persistence-capable instance. The second argument is the field name of the reference.
JDOHelper
JDOHelper was previously introduced as a way to bootstrap a JDO application and get an instance of a PersistenceManagerFactory. As well as this, JDOHelper has a number of additional methods that act as shortcuts to the various JDO APIs.
The following code snippet shows a non-persistence-capable class called Address that can be used to store the address of an Author:
import java.io.Serializable; public class Address implements Serializable { private String street; private String zip; private String state; public Address(String street, String zip, String state) { this.street = street; this.zip = zip; this.state = state; } /* Additional getters and setters not shown */ public void setZip ( String zip ) { this.zip = zip; } }
Exactly how a JDO implementation manages the fields of non-persistence-capable classes is undefined by JDO. Some implementations may require that a class implement Serializable or follow the JavaBean pattern, or may require that its fields be declared public.
The following code snippet shows a revised Author class that contains a reference to an Address and shows how to set the zip code of the address. The method first calls makeDirty() to inform the JDO implementation that the address is being be modified:
import javax.jdo.*; public class Author { private String name; private Address address; public Author(String name, Address address) { this.name = name; this.address = address; } protected Author () {} public void setZip(String zip) { JDOHelper.makeDirty(this, "address"); address.setZip(zip); } }
JDOHelper.makeDirty()
It is a best practice to call makeDirty() before making any modifications to a non-persistence-capable instance. Depending on the underlying datastore, the call to makeDirty() may result in a concurrency conflict indicating that changes aren't allowed at that time.
The revised JDO metadata for this class in Author.jdo is as follows:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE jdo SYSTEM "jdo.dtd"> <jdo> <package name="com.corejdo.examples.model"> <class name="Author"> <field name="address" persistence-modifier="persistent"/> </class> </package> </jdo>
Because the address field is a reference to a non-persistence-capable class, it is necessary to specifically specify that the field should be persistent.
When using non-persistence-capable instances, it is best to modify them only through methods on the referencing persistent object. This way, the persistent object can call makeDirty() on itself before making any changes. If a non-persistence-capable object is passed by reference and modified elsewhere, it becomes hard for the application to ensure that the referencing persistent object is notified of the changes, in which case any changes are ignored.
3.9.3 Collection classes
A persistence-capable class can have fields that reference the standard Java collection classes. Instances of these collection classes exist in the datastore only as part of the referencing persistent object. At a minimum, JDO mandates support for java.util.HashSet; support for ArrayList, HashMap, Hashtable, LinkedList, TreeMap, TreeSet, and Vector is optional. However, most JDO implementations support all these collection classes.
Optional Features
To determine whether an implementation supports the optional collection classes, use the supportedOptions() method on PersistenceManagerFactory. See Chapter 5 for more details.
The following code snippet shows a revised Author class that uses a java.util.HashSet to hold the books that an author has written:
import java.util.*; public class Author { private String name; private Set books = new HashSet(); public Author (String name) { this.name = name; } protected Author () {} public String getName() { return name; } public void setName(String name) { this.name = name; } public void addBook(Book book) { books.add(book); } public Iterator getBooks() { return books.iterator(); } }
The revised JDO metadata for this class in AuthorWithBooks.jdo is as follows:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE jdo SYSTEM "jdo.dtd"> <jdo> <package name="com.corejdo.examples.model"> <class name="Author"> <field name="books"> <collection element-type= "com.corejdo.examples.model.Book"/> </field> </class> </package> </jdo>
Because this class uses a Java collection, additional metadata can be used to declare the class of the instances that are stored in the collection. Because Java collections are un-typed, it is impossible for a JDO implementation to determine this information from the Java class itself.
Additional Metadata
The additional metadata shown for the "books" field is optional; it does not need to be specified. If not specified, the JDO runtime assumes that the collection may contain a reference to any persistent object, as does normal Java.
Even though it is optional, it is best practice to define the element type of a collection because it potentially allows the JDO runtime to optimize how it handles the field both in-memory as well as in the datastore.
Unlike user-defined, non-persistence-capable classes, JDO mandates that the supported collections automatically detect changes and notify the referencing persistent object. A JDO implementation does this by replacing instances of system-defined collection classes with instances of its own equivalent classes that perform this detection. For a new persistent object, this replacement happens during the call to makePersistent(); for existing persistent objects, this occurs when they are retrieved from the datastore. All this is completely transparent to the application. It just means that an application should not reply on a collection being a particular concrete Java class (java.util.HashMap, for example).
3.9.4 Arrays
Support for a persistence-capable class with fields that use Java arrays is optional in JDO. If supported, arrays are similar in nature to user-defined, non-persistence-capable classes except that a JDO runtime may automatically detect changes and notify the referencing persistent object of the modification. For portability, an application should assume responsibility for notifying the referencing persistent object when an array is modified.
With non-persistence-capable classes, it is a best practice to modify arrays only through methods on the referencing persistent object. This way, the referencing persistent object can call makeDirty() on itself before making any change. If an array is passed by reference and modified elsewhere, it becomes hard for the application to ensure that the owning persistent object is notified of the changes, in which case any changes are ignored.
The following code snippet shows a revised Author class that uses an array to store the books that an author has written. As a simplification, the array holds only a single book; in real life, any sized array could have been used:
import java.util.*; public class Author { private String name; private Book books[] = new Book[1]; public Author(String name) { this.name = name; } protected Author() public String getName() { return name; } public void setName(String name) { this.name = name; } public void addBook(Book book, int i) { JDOHelper.makeDirty(this, "books"); books[i] = book; } public Book getBook(int i) { return books[i]; } }
The revised JDO metadata for this class in Author.jdo is as follows:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE jdo SYSTEM "jdo.dtd"> <jdo> <package name="com.corejdo.examples.model"> <class name="Author"/> </package> </jdo>
Because the field is an array of a persistence-capable class, no additional metadata is required and the array field is persistent by default.
3.9.5 Inheritance
JDO supports inheritance of both interfaces and classes. Because Java interfaces don't define any fields, a JDO implementation needs to do nothing, so Java interfaces can be used with no real consideration as far as JDO is concerned.
When using class inheritance (abstract or otherwise), JDO needs to be aware of the implementation hierarchy, because each class can define its own fields. In the usual case, where all classes in the inheritance hierarchy are themselves persistence-capable, it is straightforward. The only requirement is to denote the persistence-capable superclass of a class in the JDO metadata.
The following code snippet shows a class that extends the Book class:
public class RevisedBook extends Book { private Book original; public RevisedBook(String name, Book original) { super(name); this.original = original; } protected RevisedBook() {} public Book getOriginal() { return original; } public void setOriginal(Book original) { this.original = original; } }
The JDO metadata for this class in RevisedBook.jdo is as follows:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE jdo SYSTEM "jdo.dtd"> <jdo> <package name="com.corejdo.examples.model"> <class name="RevisedBook" persistence-capable-superclass="Book"/> </package> </jdo>
The additional metadata denotes the persistence-capable superclass of the class. The persistence-capable-superclass name uses the Java naming rules: If no package is included in the name, the package name is assumed to be the same package as the persistence-capable class.
It is possible for a persistence-capable class to extend a non-persistence-capable class. In this case, any fields defined by the non-persistence-capable class are ignored by JDO and are not persisted in the datastore. It is also possible to have a persistence-capable class extend a non-persistence-capable class, which itself extends a persistence-capable class. In all cases, any fields of a non-persistence-capable class are ignored by JDO; it is the responsibility of the application to manage these fields.
It is best to make all classes in an inheritance hierarchy persistence-capable. This avoids added complications. If this isn't possible and non-persistence-capable classes have to be used, then a persistence-capable subclass should implement the InstanceCallbacks Interface and use the jdoPostLoad(), jdoPreStore(), and jdoPreClear() methods to manage the fields of the non-persistence-capable classes explicitly.
3.9.6 Modifiers
JDO supports all Java class and field modifiersprivate, public, protected, static, transient, abstract, final, synchronized, and volatilewith the following caveats:
-
A field declared as transient is not persisted it in the datastore by default. This default behavior can be overridden by explicitly declaring the field as persistent in the class's metadata.
-
A field declared as final cannot be persisted in the datastore; its value can be set only during construction.
-
A field declared as static cannot be persisted in the datastore; it has meaning only at the class level within the JVM.
Apart from static and transient, use of field modifiers is essentially orthogonal to the use of JDO.
3.9.7 What isn't supported
JDO supports most of what can be defined in Java. The major exception is that JDO can't make Java system classes persistence-capable; rather, it mandates support for specific system classes (as already outlined) as fields of a persistence-capable class. Also, classes that depend on an inaccessible or remote state, such as those that use JNI or those that extend system-defined classes, cannot be made persistence-capable and cannot be used as fields of persistence-capable classes.