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 | |
---|---|
Kodo supports both datastore and application 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
PersistenceManager
s, 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.
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 | |
---|---|
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,
String
s, or
Date
s. Notably, other persistent instances can
not be used as primary key fields.
Note | |
---|---|
For legacy schemas with binary primary key columns, Kodo
also supports using primary key fields of type
|
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 | |
---|---|
Though you may still create application identity classes by
hand, Kodo provides the |
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 ()); } } }
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:
Use instanceof
instead of
comparing Class
objects
in the equals
methods
of your identity classes.
An identity class that extends another
non-abstract identity class should not override
equals
or
hashCode
.
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:
byte
,
java.lang.Byte
:
javax.jdo.identity.ByteIdentity
char
,
java.lang.Character
:
javax.jdo.identity.CharIdentity
int
,
java.lang.Integer
:
javax.jdo.identity.IntIdentity
long
,
java.lang.Long
:
javax.jdo.identity.LongIdentity
short
,
java.lang.Short
:
javax.jdo.identity.ShortIdentity
java.lang.String
:
javax.jdo.identity.StringIdentity