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 datastore.

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 persistent objects represent the same state in the datastore.

Example 4.4. JDO Identity Objects

/**
 * This method tests whether the given persistent objects represent the
 * same datastore 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 datastore: the == operator. JDO requires that each PersistenceManager maintain only one JVM object to represent each unique datastore 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 datastore? 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 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 control over what class is used for JDO identity objects, and limited control over 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 implement the toString method such that passing the result to PersistenceManager.newObjectIdInstance creates a new JDO identity object that compares equal to the instance the string was obtained from.

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

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. These fields 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 you should implement it if portability is a concern.

If your class has only 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, you must supply an application identity class to use 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 class must have a string constructor, or a constructor that takes a Class, String argument pair. The optional Class argument is the target persistent class, and the string is the result of toString on another identity instance of the same type.

  • The names of the non-static fields of the class must include the names of the primary key fields of the corresponding persistent 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 persistent 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 hierarchy of the owning persistent classes (see Section 4.5.2.1, “Application Identity Hierarchies”).

  • Primary key fields must be primitives, primitive wrappers, Strings, or Dates. 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 persistent 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 provides the appidtool to automatically generate proper application identity classes based on your primary key fields. See Section 5.3.2, “Application Identity Tool” of the Reference Guide.

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 == mag.isbn
            || (isbn != null && isbn.equals (mag.isbn)))
            && (title == mag.title
            || (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 ());
    }

    // rest of fields and methods omitted


    /**
     * Application identity class for Magazine.
     */
    public static class MagazineId
    {
        static
        {
            // register Magazine with the JVM
            try { Class.forName ("Magazine") } catch (Exception e) {}
        }
    
        // 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 == mi.isbn
                || (isbn != null && isbn.equals (mi.isbn)))
                && (title == mi.title
                || (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 ());
        } 
    }
}

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 are as follows:

  • The inheritance hierarchy of application identity classes must exactly mirror the hierarchy of the persistent classes that they identify. In the example pictured above, abstract class Person is extended by abstract class Employee, which is extended by non-abstract class FullTimeEmployee, which is extended by non-abstract class Manager. The corresponding identity classes, then, are an abstract PersonId class, extended by an abstract EmployeeId class, extended by a non-abstract FullTimeEmployeeId class, extended by a non-abstract ManagerId class.

  • Subclasses in the application identity hierarchy may define additional primary key fields until the hierarchy becomes non-abstract. In the aforementioned example, Person defines a primary key field ssn, Employee defines additional primary key field userName, and FullTimeEmployee adds a final primary key field, empId. However, Manager may not define any additional primary key fields, since it is a subclass of a non-abstract class. The hierarchy of identity classes, of course, must match the primary key field definitions of the persistent class hierarchy.

  • It is not necessary for each abstract class to declare primary key fields. In the previous example, the abstract Person and Employee classes could declare no primary key fields, and the first concrete subclass FullTimeEmployee could define one or more primary key fields.

  • All subclasses of a concrete identity class must be equals and hashCode-compatible with the concrete superclass. This means that in our example, a ManagerId instance and a FullTimeEmployeeId instance with the same primary key field values should have the same hash code, and should compare equal to each other using the equals method of either one. In practice, this requirement reduces to the following coding practices:

    1. Use instanceof instead of comparing Class objects in the equals methods of your identity classes.

    2. An identity class that extends another non-abstract identity class should not override equals or hashCode.

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. All single field identity classes extend javax.jdo.identity.SingleFieldIdentity. This base type defines the following methods:

public Object getKeyAsObject ()

Returns the primary key value as an object. Each SingleFieldIdentity subclass also defines a getKey method to return the primary key in its primitive form.

public Class getTargetClass ()

The target class of a single field identity object is the persistent class to which the identity object corresponds. Note that the target class is not part of the serialized state of a SingleFieldIdentity instance. After an instance has been deserialized, calls to this method return null.

public String getTargetClassName ()

Returns the name of the target class. This method returns the correct value even after a single field identity object has been deserialized.

The following list enumerates the primary key field types supported by single field identity, and the built-in identity class for each type:

 

Skip navigation bar   Back to Top