4.5. JDO Identity

4.5.1. Datastore Identity
4.5.2. Application Identity
4.5.2.1. Application Identity Hierarchies
4.5.3. Single Field Identity

Java recognizes two forms of object identity: numeric identity and qualitative identity. If two references are numerically identical, then they refer to the same JVM instance in memory. You can test for this using the == operator. Qualitative identity, on the other hand, relies on some user-defined criteria to determine whether two objects are "equal". You test for qualitative identity using the equals method. By default, this method simply relies on numeric identity.

JDO introduces another form of object identity, called JDO identity. JDO identity tests whether two persistent objects represent the same state in the data store.

The JDO identity of each persistent instance is encapsulated in its JDO identity object. You can obtain the JDO identity object for a persistent instance through the JDOHelper's getObjectId method. If two JDO identity objects compare equal using the equals method, then the two corresponding persistent objects represent the same state in the data store.

Example 4.4. JDO Identity Objects

/**
 * This method tests whether the given persistent objects represent the
 * same data store record.  It returns false if either argument is not
 * a persistent object.
 */
public boolean persistentEquals (Object obj1, Object obj2)
{
    Object jdoId1 = JDOHelper.getObjectId (obj1);
    Object jdoId2 = JDOHelper.getObjectId (obj2);
    return jdoId1 != null && jdoId1.equals (jdoId2);
}

If you are dealing with a single PersistenceManager, then there is an even easier way to test whether two persistent object references represent the same state in the data store: the == operator. JDO requires that each PersistenceManager maintain only one JVM object to represent each unique data store record. Thus, JDO identity is equivalent to numeric identity within a PersistenceManager's cache of managed objects. This is referred to as the uniqueness requirement.

The uniqueness requirement is extremely important -- without it, it would be impossible to maintain data integrity. Think of what could happen if two different objects of the same PersistenceManager were allowed to represent the same persistent data. If you made different modifications to each of these objects, which set of changes should be written to the data store? How would your application logic handle seeing two different "versions" of the same data? Thanks to the uniqueness requirement, these questions do not have to be answered.

There are three types of JDO identity, but only two of them are important to most applications: datastore identity and application identity. The majority of JDO implementations support datastore identity at a minimum; many support application identity as well. All persistent classes in an inheritance tree must use the same form of JDO identity.

[Note]Note

Kodo JDO supports both datastore and application identity.

4.5.1. Datastore Identity

Datastore identity is managed by the JDO implementation. It is independent of the values of your persistent fields. You have no say over what class is used for JDO identity objects, or what data is used to create identity values. The only requirement placed on JDO vendors implementing datastore identity is that the class they use for JDO identity objects meets the following criteria:

  • The class must be public.

  • The class must be serializable.

  • All non-static fields of the class must be public and serializable.

  • The class must have a public no-args constructor.

  • The class must have a public String constructor. It must override the toString method to return a string that can be used by this constructor to create a new JDO identity object that compares equal to the instance the string was obtained from.

The last criterion listed is particularly important. As you will see in the chapter on PersistenceManagers, it allows you to store the identity object for a persistent instance as a string, then later recreate the identity object and retrieve the corresponding persistent instance.

[Note]Note

Kodo JDO allows you to customize the manner in which datastore identity values are generated.

4.5.2. Application Identity

Application identity is managed by you, the developer. Under application identity, the values of one or more persistent fields in an object determine its JDO identity. The fields whose values make up the object's identity are called primary key fields. Each object's primary key field values must be unique among all other objects of the same type.

When using application identity, the equals and hashCode methods of the persistence-capable class must depend on all of the primary key fields.

[Note]Note

Kodo does not depend upon this behavior. However, some JDO implementations do, so it should be implemented if portability is a concern.

If you have one one primary key field, you can use single field identity to simplify working with application identity (see Section 4.5.3, “Single Field Identity”). Otherwise, it is up to you to supply the class used for JDO identity objects. Your application identity class must meet all of the criteria listed for datastore identity classes. It must also obey the following requirements:

  • The names of the non-static fields of the class must include the names of the primary key fields of the corresponding persistence-capable class, and the field types must be identical.

  • The equals and hashCode methods of the class must use the values of all fields corresponding to primary key fields in the persistence-capable class.

  • If the class is an inner class, it must be static.

  • All classes related by inheritance must use the same application identity class, or else each class must have its own application identity class whose inheritance hierarchy mirrors the inheritance huerarchy of the owning persistent classes (see Section 4.5.2.1, “Application Identity Hierarchies”.

  • Primary key fields must be primitives, primitive wrappers, or Strings. Notably, other persistent instances can not be used as primary key fields.

[Note]Note

For legacy schemas with binary primary key columns, Kodo also supports using primary key fields of type byte[].

These criteria allow you to construct an application identity object from either the values of the primary key fields of a persistent instance, or from a string produced by the toString method of another identity object.

Though it is not a requirement, you should also use your application identity class to register the corresponding persistence-capable class with the JVM. This is typically accomplished with a static block in the application identity class code, as the example below illustrates. This registration process is a workaround for a quirk in JDO's persistent type registration system whereby some by-id lookups might fail if the type being looked up hasn't been used yet in your application.

[Note]Note

Though you may still create application identity classes by hand, Kodo JDO provides the appidtool to automatically generate proper application identity classes based on your primary key fields.

Example 4.5. Application Identity Class

/**
 * Persistent class using application identity.
 */
public class Magazine
{
    private String isbn;    // primary key field
    private String title;   // primary key field

    /**
     * Equality must be implemented in terms of primary key field
     * equality, and must use instanceof rather than comparing 
     * classes directly.
     */
    public boolean equals (Object other)
    {
        if (other == this)
            return true;
        if (!(other instanceof Magazine))
            return false;

        Magazine mag = (Magazine) other;
        return ((isbn == null && mag.isbn == null)
            || (isbn != null && isbn.equals (mag.isbn)))
            && ((title == null && mag.title == null)
            || (title != null && title.equals (mag.title)));
    }
     
    /**
     * Hashcode must also depend on primary key values.
     */
    public int hashCode ()
    {
        return ((isbn == null) ? 0 : isbn.hashCode ())
            + ((title == null) ? 0 : title.hashCode ())
            % Integer.MAX_VALUE;
    }

    // rest of fields and methods omitted


    /**
     * Application identity class for Magazine.
     */
    public static class MagazineId
    {
        static
        {
            // register Magazine with the JVM
            Class c = Magazine.class;
        }
    
        // each primary key field in the Magazine class must have a
        // corresponding public field in the identity class
        public String isbn;
        public String title;

        /**
         * Default constructor requirement.
         */
        public MagazineId ()
        {
        }
    
        /**
         * String constructor requirement.
         */
        public MagazineId (String str)
        {
            int idx = str.indexOf (':');
            isbn = str.substring (0, idx);
            title = str.substring (idx + 1);
        }
    
        /**
         * toString must return a string parsable by the string constructor.
         */
        public String toString ()
        {
            return isbn + ":" + title;
        }
    
        /**
         * Equality must be implemented in terms of primary key field
         * equality, and must use instanceof rather than comparing 
         * classes directly (some JDO implementations may subclass JDO
         * identity class).
         */
        public boolean equals (Object other)
        {
            if (other == this)
                return true;
            if (!(other instanceof MagazineId))
                return false;
    
            MagazineId mi = (MagazineId) other;
            return ((isbn == null && mi.isbn == null)
                || (isbn != null && isbn.equals (mi.isbn)))
                && ((title == null && mi.title == null)
                || (title != null && title.equals (mi.title)));
        }
     
        /**
         * Hashcode must also depend on primary key values.
         */
        public int hashCode ()
        {
            return ((isbn == null) ? 0 : isbn.hashCode ())
                + ((title == null) ? 0 : title.hashCode ())
                % Integer.MAX_VALUE;
        } 
    }
}

4.5.2.1. Application Identity Hierarchies

An alternative to having a single application identity class for an entire inheritance hierarchy is to have one application identity class per level in the inheritance hierarchy. The requirements for using a hierarchy of application identity classes is as follows:

  • The inheritance hierarchy of application identity classes must exactly mirror the hierarchy of the persistent classes that they identify. For example, if there is abstract class Person, abstract class Employee that extends Person, non-abstract class FullTimeEmployee that extends Employee, and non-abstract Manager that extends FullTimeEmployee, then the corresponding application identity classes might be an abstract PersonID class, and abstract EmployeeID that extends PersonID, a non-abstract FullTimeEmployeeID class that extends EmployeeID, and a non-abstract ManagerID class that extends FullTimeEmployeeID.

  • Subclasses in the application identity hierarchy may define additional primary key fields until the hierarchy becomes non-abstract. In the aforementioned example, PersonID may define an ID field called pk1, EmployeeID may define an additional primary key field called pk2, and FullTimeEmployeeID can further define an additional primary key field called pk3. However, ManagerID may not define any additional primary key fields, since it is a subclass of a non-abstract class.

    [Note]Note

    When defining additional primary key fields in subclasses of application identity classes, Kodo requires that you use the "horizontal" class mapping (see Section 7.6.4, “Horizontal Inheritance Mapping”).

  • It is not necessary for each abstract application identity class to declare primary key fields. In the previous example, the absract PersonID and EmployeeID classes could declare no primary key fields, and the first concrete subclass FullTimeEmployeeID could define one or more primary key fields.

4.5.3. Single Field Identity

Single field identity is a subset of application identity. When you have only one primary key field, you can choose to use one of JDO's built-in single field identity classes instead of coding your own application identity class. The following list enumerates the primary key field types supported by single field identity, and the built-in identity class for each type:

  • byte, java.lang.Byte: javax.jdo.ByteIdentity

  • char, java.lang.Character: javax.jdo.CharIdentity

  • int, java.lang.Integer: javax.jdo.IntIdentity

  • long, java.lang.Long: javax.jdo.LongIdentity

  • short, java.lang.Short: javax.jdo.ShortIdentity

  • java.lang.String: javax.jdo.StringIdentity

[Note]Note

Single field identity is a planned JDO 2 feature. It is possible that it will change before the JDO 2 specification is finalized. Official JDO 2 jars are not available yet; until they are you must use the Kodo-supplied single field identity classes rather than the official classes above: kodo.util.ByteIdentity, kodo.util.CharIdentity, kodo.util.IntIdentity, kodo.util.LongIdentity, kodo.util.ShortIdentity, kodo.util.StringIdentity.

These Kodo-specific classes will be removed once JDO 2 jars become available.