Some form of persistent object identity is required for each instance. Applications use this to retrieve specific instances from the data store. Object identity can also be used to maintain uniqueness constraints over the domain objects where this is warranted (e.g. no two orders with the same order number).
In the Java language there are two forms of identity: equality and equivalence.
Equality is used to determine whether two Java references actually point to the same object in memory, and makes use of the == operator. Equality does not take into account the state (attribute values) of an object and cannot traverse JVM processes in distributed applications. As such it is limited in its application.
Equivalence compares two (potentially non-equal) objects to determine whether they both represent the same logical object, and makes use of the equals() method. Most applications already employ equality for their own comparisons.
In order to improve the transparency with which JDO can be applied to existing object models, JDO defines its own concept of identity. Both of the above techniques remain unaffected.
The three different types of identity defined by JDO are application identity, datastore identity, and non-durable identity. The desired identity for each instance is specified in the persistence descriptor.
Internally the JDO implementation is responsible for ensuring that there is, at most, one persistent JDO instance associated with a specific data store object per persistence manager. This process is referred to as uniquing. The object that encapsulates the identity of an instance is known as its Object ID, and the underlying class definition its Object ID class.
3.7.1 Datastore identity
Datastore identity is the default identity mechanism. Identity is ascribed to the object when it is made persistent. The manner in which this is achieved, and the nature of the Object ID Class, are internal to the JDO implementation and the data store. However, once the identity is determined it can be used in future requests to retrieve that particular object.
Datastore identity is typically used for dependent objects. For instance, an Order may have application identity (see 3.7.2) with a primary key comprised of its order number, and the OrderLine objects contained within the order may have datastore identity. Two OrderLine objects remain distinct from one another through their internally-assigned identity (Figure 3.3).
Figure 3.3. Order composition of OrderLine
Indeed this is such a common arrangement that it should be considered wherever the UML composition relationship, indicated by the solid black diamond, is used.
3.7.1.1 Example
In our simple example, the BusinessPartner class has datastore identity by default. The identity type can be specified explicitly with the identity-type attribute of the <class> tag, as shown below.
BusinessPartner.jdo
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE jdo SYSTEM "file:///jdowork/dtd/jdo.dtd"> <jdo> <package name="com.ogilviepartners.jdobook.op"> <class name="BusinessPartner" identity-type="datastore" /> </package> </jdo>
3.7.2 Application identity
With application identity the application is responsible for the identity of an instance, which is derived from the values of a subset of its persistent fields. The persistence descriptor is used to specify one or more persistent fields that will make up a primary key for the instance, alongside a name for the class that will act as the Object ID class. As such, application identity is often referred to as primary key identity, and the Object ID class for such an instance is often referred to as the primary key class. The application developer usually writes the primary key class, although some enhancer tools are capable of generating them when enhancing classes that use application identity.
Some restrictions exist regarding the primary key class. The class must be public and implement java.io.Serializable. A no-argument constructor and a string constructor must both exist. The toString() method must have been overridden so that the string it returns can be used as an argument to the string constructor, in order to create an equivalent instance of the primary key. (The actual format for this string is the developer’s choice.) All non-static fields must be primitives, or references to Serializable classes, and must be public.
For every field identified in the persistence descriptor as a primary key field there must be a corresponding and identically named field in the primary key class; all of these (and only these) fields must be utilized by the primary key class’s equals() and hashcode() methods for the determination of equivalence. There may be additional fields in the primary key class, but this is unusual as they would play no part in the uniquing process.
The restrictions above enable a JDO primary key class to be interchangeable with an Entity Bean primary key class, simplifying the integration of JDO with the EJB architecture. This integration is covered in detail in Chapter 11.
3.7.2.1 Example
By virtue of its default datastore identity, the uniquing of BusinessPartner instances has nothing to do with attribute values. Thus multiple BusinessPartner instances may share the same partner number. Each one will have its own unique Object ID assigned by the JDO implementation when it was made persistent.
We can now correct this by assigning application identity to the BusinessPartner class with a primary key comprised of its partner number. First of all, I show below the primary key class BusinessPartnerPK. I have chosen to put the primary key classes into a pk subpackage.
BusinessPartnerPK.java
package com.ogilviepartners.jdobook.op.pk; public class BusinessPartnerPK { public String partnerNumber; public boolean equals(Object that) { try { return equals((BusinessPartnerPK) that); } catch (ClassCastException cce) { return false; } } public boolean equals(BusinessPartnerPK that) { if (that == null) return false; if (this == that) return true; // "equality" within // the JVM if (this.partnerNumber == null) return that.partnerNumber == null; return this.partnerNumber.equals(that.partnerNumber); } public int hashCode() { return partnerNumber.hashCode(); } public BusinessPartnerPK(String arg) { this.partnerNumber = arg; } public BusinessPartnerPK() { } public String toString() { return partnerNumber; } }
It is possible for an enhancer tool to generate Object ID classes when the developer has not already written these. I perceive this as a good approach and very rarely hand-write such classes. However, developers should be aware that applications tend to have compile-time dependencies on the primary key classes, and so the enhancement phase must have occurred prior to compiling the application classes.
Here then is the persistence descriptor that configures the application identity for BusinessPartner. The persistent fields that are part of the primary key must now be explicitly listed. Any unlisted fields will be made persistent or not according to the defaults mentioned in Chapter 2.
BusinessPartner.jdo
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE jdo SYSTEM "file:///jdowork/dtd/jdo.dtd"> <jdo> <package name="com.ogilviepartners.jdobook.op"> <class name="BusinessPartner" identity-type="application" objectid-class="com.ogilviepartners/ .jdobook.op.pk.BusinessPartnerPK"> <field name="partnerNumber" primary-key="true" /> </class> </package> </jdo>
3.7.3 Non-durable JDO identity
Non-durable JDO identity is used for persistent objects where it is meaningless to try to distinguish one from another. Since the determination and creation of a data store key can be a resource-intensive operation, non-durable JDO identity is most often used to support the rapid persistence of new instances.
An example of this might be the implementation of a JDO instance that represents system alert messages. By using non-durable JDO identity these objects could be persisted very rapidly.
Many alerts, most of these duplicates of previously persisted alerts, may be created over a period of time. The semantics of manipulating instances with non-durable JDO identity are that if one is made persistent then there is one more persistent instance than there was before. Equally, if one is deleted then there is one fewer persistent instance than there was before.
Thus an application may facilitate the selection and deletion of a particular alert containing the message “Sales Transaction Abandoned.” Once this operation has committed there will be one fewer alert with the message “Sales Transaction Abandoned” than there was previously. We are not in the least bit concerned with which particular one of potentially many such alerts (all with identical persistent field values) was deleted.
3.7.3.1 Example
Below is a sample persistence descriptor for a hypothetical AlertMessage class for which non-durable JDO identity is required:
AlertMessage.jdo
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE jdo SYSTEM "file:///jdowork/dtd/jdo.dtd"> <jdo> <package name="com.ogilviepartners.jdobook.op"> <class name="AlertMessage" identity-type="nondurable" /> </package> </jdo>
3.7.1 JDO identity comparison
Table 3.3 contrasts the three different JDO identity types.
Table 3.3. JDO identity comparison
Datastore identity |
Application identity |
Non-durable identity |
|
---|---|---|---|
Is the default |
|||
Uniquely identifies persistent instances |
|||
Uses an Object ID Class |
|||
Developer can provide Object ID class |
|||
Identity determined by attribute values |
|||
Subverts uniquing process for rapid persistence of transient instances |