Identity and the Uniqueness Requirement


When JDO manages a persistent object, it periodically synchronizes the state of the object with the state in the datastore. How does JDO match up objects in memory with the state in the datastore? Is it possible for two objects in memory to refer to the same state in the datastore? By understanding JDO identity and the uniqueness requirement, you can answer these questions.

In Java, two references to objects may refer to the same object or distinct objects in memory. Likewise in JDO, two distinct persistent objects in memory may refer to the same or distinct datastore objects. The term "datastore object" is really shorthand for the longer phrase "the object's persistent state in the datastore." Persistent state in the datastore usually does not look or act anything like a Java object, but it is convenient to use the shorthand in place of the more precise term.

In the Java language, the == operator determines whether two references refer to the same object in memory or distinct objects in memory. On the other hand, to determine whether two persistent objects in memory refer to the same or distinct datastore objects requires the use of the JDO identity. JDO identity is implemented in a separate object, called the identity object, that JDO associates with the persistent object. The association between a persistent object and its JDO identity object is show in Figure 1-4. All persistent application data objects have a JDO identity. Application data objects that are not persistent, which includes those that are JDO-transient, transient-clean, and transient-dirty, do not have a JDO identity. The objects of supported system classes never have a JDO identity.

click to expand
Figure 1-4: Two application data objects referring to the same persistent state

By definition in JDO, two persistent application data objects in memory refer to the same datastore object if their JDO identity objects satisfy the equals method. In the JDO identity object's equals method, the values of one or more fields are compared. The values of these fields are collectively called the identity value. In Figure 1-4, the identity value is shown as contained within the JDO identity object. Because the two JDO identity objects in Figure 1-4 satisfy the equals method, their identity values are the same, and for that reason, the application data objects represent the same persistent state in the datastore. By definition, there is no such thing as two distinct persistent states in the datastore with the same identity value.

JDO does not hand off to the application a reference to the JDO identity object that JDO uses internally. Instead, when the application asks for a JDO identity object, it receives a copy of the internal identity object. For that reason, the application never has the opportunity to alter how JDO links the persistent object to its persistent state in the datastore.

The Three Types of JDO Identity

There are three flavors of JDO identity, and the JDO metadata associates each application data class with one of these flavors. The first two flavors, datastore identity and application identity, are called the durable JDO identities. As the name implies, the durable identity value is stored in the datastore. As would be expected, the datastore ensures that all durable identity values are unique. The third flavor of JDO identity is nondurable identity. As its name implies, its value is not stored in the datastore. Indeed, the purpose of nondurable identity is to avoid the overhead of storing unique identity values in the datastore.

All of the application data classes that are related by inheritance to another application data class must use the same flavor of JDO identity as the least-derived application data class in its inheritance hierarchy. For example, if Truck and Car are application data classes that inherit from the Vehicle application data class, then the flavor of JDO identity used for Vehicle must also be used by Truck and Car. On the other hand, if Truck and Car inherit from Object instead, then they could use different flavors of JDO identity, because applications do not enhance the Object class.

Every JDO implementation must support one of the two durable identity types. It may, at its option, support either or both of the other two identity types. Chapter 6 demonstrates how to determine the options that a particular implementation supports.

Datastore Identity

When datastore identity is used, the JDO implementation takes complete responsibility for managing the object identities for the application data class. The identity class is defined by the implementation, and the JDO implementation selects a mechanism for generating the identity values. As a result, the application programmer will find it convenient to use the datastore identity in many cases.

Enhancement does not add any additional instance fields to the application data class to hold the datastore identity. Because the identity value is not stored in the application data object, the datastore identity value is opaque to the application data class. The application has no access to its type or value, except by reflection on the identity object. JDO does not permit the application to change the value of a datastore identity. Different implementations are very likely to have different datastore identity classes and use different algorithms for calculating the identity value. For that reason, it is not meaningful to compare the equality of datastore identity objects that come from different implementations.

The JDO implementation may impose additional restrictions on when it is meaningful to compare the equality of datastore identities. For example, if the datastore reassigns identity values during reorganization, it is not meaningful to compare a datastore identity obtained before the reorganization with a datastore identity obtained after the reorganization.

Although JDO gives implementations a free hand in the details of implementing the datastore identity class, any datastore identity class must meet the following seven requirements:

  • It must be public.

  • It must implement Serializable.

  • It must have a public no-argument (no-arg) constructor.

  • All of its instance fields must be serializable and public.

  • It must have a constructor that accepts one string.

  • Its toString method must return a string that can be passed to its string constructor, as well as to the persistence manager's newObjectIdInstance method, to obtain a new identity object that is equal to the original object from which the string was obtained.

  • Its equals method must return true if and only if the two datastore identity objects contain the same identity value, and its hashCode method must return the same integer value for different identity objects when they contain the same identity value.

Two of the requirements on the list are especially interesting. First, the datastore identity object is serializable. Hence, it can be stored and passed to other JVMs. Second, the toString method returns a string that can be used to re-create the identity object. In this book, this string value is called the identity string. An identity string is very useful when dealing with HTML clients, because you can round-trip the string to them. This gives you the opportunity to avoid holding state on the server for each client. Datastore identity, like application identity, is a magic cookie. The application can pass it to the persistence manager's getObjectById method to obtain the corresponding persistent object.

It is tempting to view the identity string as a surrogate for the identity object, but this is not strictly the case. While it is true that equality of identity strings necessarily implies the equality of the identity objects that can be obtained with them, it is not true that equality of the identity objects implies the equality of the identity strings that they produce. The reason for the asymmetry is this: the string constructor in the datastore identity class can parse the string. Therefore, the toString method can safely construct a string that contains extraneous information. The constructor would ignore the extra information when creating the identity object. As a result, identity strings returned from equivalent identity objects (or even two strings from the same object at different times) could turn out not to satisfy the equals method of the String class. Although the specification allows this nonisomorphic behavior by its silence, most implementations are, in fact, providing isomorphic mappings between identity objects and their identity strings.

Application Identity

The responsibility for defining and managing application identity is shared between the application and the JDO implementation. The identity value of application identity is composed of the value of one or more primary key fields declared in the application data class. The primary key fields are persistent fields in the application data class whose values, taken together, are unique for each object in the class of objects.

The responsibility for managing the uniqueness of the identity value is shared between the JDO implementation and the application. The datastore fails any transaction that attempts to store an object whose primary key is already being used by another object of the same class. The application retains responsibility for creating unique application identities. Some implementations allow the application to alter the key's fields containing the application identity value after the object becomes persistent.

When the application makes persistent an unmanaged application data object that uses application identity, JDO constructs the application identity object using the values of the primary key fields in the data object.

Note

The implementation supports changing the application identity after the object becomes persistent if the implementation returns the "javax.jdo.option.ChangeApplicationIdentity" string from the supportedOptions method in the PersistenceManagerFactory interface. Chapter 6 describes this interface and its operations.

The JDO metadata identifies which application data classes use application identity, which fields in the application data class are key fields, and which class is used for the application identity. In some cases, the JDO implementation will provide a tool to generate the application identity class. In other cases, the programmer must define the class. Either way, the identity class used by JDO at runtime may be a subclass of the defined application identity class.

The application identity class must meet the first six requirements of the datastore identity class. JDO also requires that application identity classes meet three additional requirements:

  • The instance fields in the identity class must include the key fields in the application data class, and their names and types must be the same.

  • The equals and hashCode methods must use all of the fields that correspond to the key fields in the application data class.

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

Application identity provides two advantages over datastore identity. One, application identity classes are portable across JDO implementations. Because of portability, comparisons between the application identity objects are always meaningful, even across JDO implementations. Porting an application that uses application identity to a subsequent JDO implementation is easier because the application can continue to use the same identity values. It is usually not possible to continue using the same datastore identity values after changing JDO implementations. The second advantage of application identity arises because the application may know the key values before it has the persistent object. Because it is trivial for the application to construct an application identity object when it has the key values, application identity is very useful for retrieving individual persistent objects when their key values are known.

Regardless of the type of identity, JDO constructs the identity objects as needed. In the case of application identity, the application is responsible for ensuring that any new application data objects added to the datastore have unique primary keys. If a new object has keys that duplicate the keys of an existing persistent object, the transaction's commit will fail by throwing a JDO exception. The specification is not clear about the type of JDO exception to expect.

Example of Application Identity

Creating an application identity class that meets all nine requirements is straight-forward. For example, consider the Heffalump class in Listing 1-3.

Listing 1-3: The Heffalump Class and Its Application Identity Class

start example
 public class Heffalump    {    private String name;   // primary key    private String color;  // persistent fields    private int    snozzle_length; static public class HeffalumpOID implements java.io.Serializable    {    public String name;    public HeffalumpOID()       {       }    public HeffalumpOID(String name)       {       if (name == null || name.length() <= 0)          throw new IllegalArgumentException(                "The name may not be null or empty");       this.name = name;       }    public boolean equals(Object other)       {       // if two references to the same object, they must be equal       if (other == this)          return true;       // if the name field is not defined, then they can't be equal       if (name == null)          return false;       // Note the use of the instanceof operator below.       // This use allows an object of a subclass of HeffalumpOID       // to compare equal.       // The instanceof test also catches the case where other is null.       if (!(other instanceof HeffalumpOID))          return false;       HeffalumpOID hoid = (HeffalumpOID) other;       return name.equals(hoid.name);       }    public int hashCode()       {       return name.hashCode();       }    public String toString()       {       return name;       }    } public Heffalump(String name, String color, int snozzleLength)    {    if (name == null || name.trim().length() <= 0)       throw new IllegalArgumentException(             "The heffalump's name cannot be null or empty");    this.name = name.trim();    this.color = color;    snozzle_length = snozzleLength;    } //... additional behavior for the Heffalump class } 
end example

The Heffalump class in Listing 1-3 uses name as its one key field. Multiple key fields might have been used, but the Heffalump is a simple class. HeffalumpOID, the heffalump's application identity class, meets all nine requirements. In its equals method, the HeffalumpOID class checks that the key field is not null. The check is required because of the availability of a public no-arg constructor. The equals method uses instanceof to check for class compatibility. Since the JDO implementation may extend the application identity class, polymorphic equality is needed to ensure that an instance created by the application and an instance created by JDO are equal when their identity values are the same.

Recommended Types for Key Fields

The JDO specification recommends that the key fields be drawn from the following types:

  • Primitives

  • String

  • Date

  • Byte

  • Short

  • Integer

  • Long

  • Float

  • Double

  • BigDecimal

  • BigInteger

The JDO implementation may choose to support additional types of key fields.

Using Application Identity for Related Compound Keys

Although the list of recommended types for key fields is long, JDO cannot handle, in an ideal fashion, related compound keys. The difficulty is best illustrated by an example.

To model a company's personnel structure, there may be a Division object and a Team object. Each Division has a name that is unique within the company. The name is the division's primary key. Each Team has a name that is unique within the Division. The primary key for the team is the division name together with the team name. Each Division has a collection of the Teams within it, and each Team has a reference to the Division to which it belongs.

What is the best way to define the application identity class for Team? The tempting choice is to declare a compound key for Team, as shown in the following code:

 Division division;  // reference to this team's division                     // and  first part of primary key String   name;      // this team's name                     // and second part of primary key 

Given the key fields for Team, the rules for application identity require the following field declarations in the team's identity class:

 public Division division; public String name; 

But Division is not a simple type; instead, it is another application data class. It may not implement the Serializable interface. Even when it does, applications usually do not want a business object contained within an identity object. The Division class does not come from the recommended field types for application keys, and for the reasons mentioned, its use is suspect. Ideally, JDO would map the Division field in the Team class to the division's key field in the team's identity class, but JDO 1.0 does not support this mapping.

The better choice for the team's application identity class is to declare the division's key field divisionName in the Team object, as shown in the following code:

 String divisionName;  // name of this team's division                       // and  first part of primary key String name;          // this team's name                       // and second part of primary key Division division;    // reference to this team's division                       // but not a primary key 

By using the divisionName and name fields as the key fields for the Team class, the team's identity class can conform to all of the required and recommended constraints for application identity.

Declaring both a divisionName and a reference to a Division introduces duplication of information in the Team class. The duplication requires some synchronization code. For example, the divisionName should be set when the Team.setDivision method is called. If the application allows a Division to change its name (this is allowing it to change its primary key), then the setter method will have to ensure that the new name is propagated to all of the division's teams. The need for synchronization code for redundant information is clearly not ideal. A future revision of JDO may improve how JDO handles related compound keys.

Using Application Identity in Related Data Classes

JDO imposes complex constraints on the use of application identity for application data classes that are related by inheritance. Implementations may not break any of these rules, but they may have rules of their own that are more restrictive. Application developers that want a thorough understanding of how to use application identity in inheritance trees of application data classes really need to understand three things. What are the most restrictive rules for defining application identity in inheritance trees? If you follow these rules, then your use of application identity will work everywhere. What are the most liberal rules for defining application identity in inheritance trees? If you require behavior that breaks any of these rules, then you will not find a JDO implementation that supports your strategy. Finally, what are the rules that limit the JDO implementation that you have selected for your application? The implementation's rules may fall at either extreme or in the middle.

Chapter 5 explains the most liberal rules that JDO allows. This section describes the most restrictive rules that work everywhere. There are only two rules that you need to follow for portability.

First, application data classes that are not derived, directly or indirectly, from the same application data class should use distinct application identity classes. If you follow this rule, you will not associate in the JDO metadata the same application identity class with different application data classes.

Second, the application should associate the least-derived application data class, whether abstract or concrete, with a concrete application identity class. When you follow this rule, the application data classes that derive from the least-derived application data class cannot declare additional key fields. Instead, all of the key fields are declared in the least-derived application data class.

Note

The objectid-class attribute of the JDO metadata's class tag associates an application data class with an application identity class.

If these two rules are too restrictive for your application, then you may want to understand the more liberal and complex constraints explained in Chapter 5, or you may want to explore the limitations of your selected JDO implementation.

Nondurable Identity

Providing for nondurable identity is an optional feature of the JDO implementation. As the name suggests, the identity value of nondurable identity is not saved in the datastore with the persistent state. The reason for using this type of JDO identity is to achieve the highest efficiency when inserting records into the datastore. It is suitable for things like log entries that are primarily write-once.

Because nondurable identity is not stored in the datastore, there is no guarantee that the same identity value will be used to refer to the same persistent object state on multiple occasions. Data objects with nondurable identity can participate in only one transaction. If a query that fetches a data object with a nondurable identity is run twice, the second data object returned is different from the first, even though they both represent the same datastore state. A nondurable identity can be used only within the persistence manager that issued it and only within the transaction in which it was obtained.

The JDO implementation defines the nondurable identity class. The JDO specification imposes a few requirements on the nondurable identity class:

  • It must be public.

  • It must have a public no-arg constructor.

  • All of its instance fields must be serializable and public.

Although applications may use nondurable JDO identities for special cases, as a rule, applications use the two types of durable identities for their application data classes.

Note

The type of JDO identity used for an application data class is specified in the JDO metadata and is fixed at class enhancement time. Chapter 5 describes the JDO metadata.

The Uniqueness Requirement

Given either the persistent object or its identity object, JDO can provide the application with the other. By comparing their identity objects with the equals method, the application can determine whether two persistent objects refer to the same persistent state in the datastore. Just as two references refer to the same object in memory when they satisfy the == operator, two application data objects in memory refer to the same state in the datastore when their identity objects satisfy the equals method. Likewise, two distinct objects in memory refer to different states in the datastore when the comparison of their identity objects is valid, but comparing them with their equals method returns false.

The type of JDO identity determines when the identity object's equals method performs a meaningful comparison. For either datastore or application identity, the equality test is valid across queries, transactions, and persistence managers. For application identity, the equality test is also valid across JDO implementations. For distinct nondurable identity objects, the equality test is valid only when it returns true.

Knowing how to tell when two data objects refer to the same persistent state is good, but knowing when there may be, or when there may not be, a multiplicity of data objects that all refer to the same persistent state is better. In short, does JDO limit the potential for multiplicity?

Although the equality test between identity objects is robust, JDO provides a simpler test that handles the typical case. This simpler test exists because JDO imposes the uniqueness requirement on JDO implementations. To understand the uniqueness requirement, consider the following conditions:

  • Let A and B be two (possibly distinct) persistent objects in the memory of the same application data class.

  • Let the application data class use a durable identity, either datastore or application.

  • Let A and B be managed by the same persistence manager.

  • Let A' and B' be the two (possibly distinct) persistent states in the datastore that A and B refer to respectively.

Under these conditions, the uniqueness requirement is the rule that if A' and B' are the same persistent state, then A and B must be the same object in memory. In other words, every persistence manager creates no more than one persistent object for any persistent state.

The uniqueness requirement applies regardless of how the references to persistent objects were obtained, as long as the application uses the same persistence manager to obtain them. The application may obtain the references from a query, by fetching an object by identity, by making a transient object persistent, or by navigating the persistent fields of other persistent objects.

Note

The uniqueness requirement does not prevent garbage collection of persistent objects. The implementation does not hold a strong reference to persistent objects that are hollow or persistent-nontransactional. When the application does not hold a strong reference to these objects, they may be garbage collected. After garbage collection, if the application again retrieves the same datastore objects, JDO will create new persistent objects with an equivalent durable identity. The creation of duplicates after garbage collection has no effect on application logic, since the application cannot have at the same time references to both the garbage-collected object and the duplicate object.

In the typical case, application code handles the persistent objects of one persistence manager at a time. In this case, a test for the object identity (==) of the possibly distinct application data objects is sufficient to determine whether the possibly distinct application data objects refer to the same or different persistent states in the datastore.

The Uniqueness Requirement Simplifies Application Code

The uniqueness requirement simplifies the application logic that manipulates persistent objects. Within the confines of one persistence manager, the term "same object" means both the same object in the datastore and the same object in memory. The uniqueness requirement ensures that either both conditions are satisfied or neither condition is satisfied.

The uniqueness requirement is notable for the issues that the application code avoids because this requirement is enforced. If there were multiple application data objects within the same persistence manager that referred to the same datastore object, then who is responsible for ensuring that they have the same representation? In the event of inconsistent changes in duplicate objects, which object updates the datastore? The uniqueness requirement prevents these issues from arising when durable identities are used. Each persistence manager can manage at most one object with a durable identity for any persistent state. When two distinct application data objects represent the same state in the datastore, but the objects are managed by distinct persistence managers, then the inconsistencies between the two representations are handled by the transactional semantics, which Chapter 4 details.

The uniqueness requirement is Ockham's razor in JDO. William of Ockham was a fourteenth-century philosopher who remains famous for saying in an ontological debate that objects should not be created needlessly. His rule became known as Ockham's razor. In JDO, a persistence manager maintains the simplest representation in memory of persistent state because it never creates more than one persistent object to represent the same persistent state.

Satisfaction of the uniqueness requirement is what allows the application to rely on JDO to simply and consistently represent persistent relationships in memory. Remember the earlier example of two persons owning the same dog. If two persons a and b are fetched within the same persistence manager, and they own the same dog, then the truth of the expression a.myDog == b.myDog is the simple and consistent way that JDO represents in memory the relationship in the datastore.

No Uniqueness Requirement for Nondurable Identity

If nondurable identity is used, then multiple application data objects may represent the same state in the datastore and be managed by the same persistence manager. As a result, it is possible for two persistent objects, using nondurable identity and managed by the same persistence manager, to have inconsistent changes for the same persistent state. The JDO implementation is responsible for throwing an exception if it is asked to commit any transaction where this situation arises. (Although the specification is not clear, the thrown exception is likely to be a JDOUserException.) It is the application's responsibility to prevent this error condition from occurring to avoid failures during commit.

Linking a Transient Object to Persistent State

So far the discussion has centered around application data objects in memory and how they refer to persistent state in the datastore. The JDO identity is the link from the persistent application data object to its persistent state. On the other hand, transient application data objects do not have a JDO identity, and therefore JDO does not recognize any linkage from them to persistent state. In some situations, as when application data objects are passed by value, the application may find it necessary to remember the JDO identity even after JDO has discarded it.

Application data objects are not always persistent. As described earlier, there are many ways that application data objects become transient. However it happens, upon becoming transient, the application data object loses its JDO identity.

Although the transient application data object loses its identity, the notion that it still represents a particular persistent state in the datastore can remain. This can be seen by considering an example where the business service uses serialization to communicate with the client. Suppose the application builds a remote service to examine and modify customer information using JDO. This service finds Customer objects and allows the client to modify the customer's information. As mentioned earlier, serialization delivers only unmanaged objects to the recipient. As a result, the remote client views, modifies, and returns to the service unmanaged Customer objects. When the service is called to modify a customer, it receives a JDO-transient Customer object from the client. There can be no doubt about the application's intent: the returned Customer object represents the same customer in the datastore as the original Customer object that the service fetched and sent via serialization to the client. Hence, the notion that the returned Customer object represents a particular persistent state in the datastore has not disappeared, but its association with its JDO identity has disappeared.

The fact that an application data object can lose its JDO identity may seem counterintuitive, or at least unhelpful. There are times when the application's intent clearly justifies saying that a transient application data object refers to a persistent state in the datastore, even when JDO does not provide the JDO identity object to support this assertion. A future version of JDO will likely address this issue. Until then, JDO provides features that the application can use to create or remember the JDO identity associated with a transient application data object.

Application identity makes it easy to produce the identity object that goes with a transient application data object. Since the primary key fields are in the application data object, it is usually trivial to ensure that they always contain valid values. JDO will require that the key fields are loaded when the object is loaded from the datastore or inserted into the datastore. The application can do its part by requiring that the key fields be supplied to construct the object. As a result, the identity value should be present in the primary key fields of every application data object that has application identity. Using the values of the key fields, the application can construct the application identity object whenever it likes.

When the application data class uses datastore identity, the situation is more complex. In this case, the application data object must capture its identity object, or the corresponding identity string, while the object is still persistent. JDO provides two ways to obtain the identity object, the persistence manager's getObjectId method and the getObjectId method in the JDOHelper class. After obtaining the datastore identity object, it should be stored in an unmanaged and serializable instance field of the application data object. Once stored, any copies made of the object will have a reference to its identity object. The callback methods defined in the InstanceCallbacks interface, which is described in Chapter 7, can be used as triggers to capture the identity object or its corresponding identity string. Chapter 7 presents an example of this usage.




Using and Understanding Java Data Objects
Using and Understanding Java Data Objects
ISBN: 1590590430
EAN: 2147483647
Year: 2005
Pages: 156
Authors: David Ezzio

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net