JDO provides methods to map between identity instances and their associated persistent instances, and between an identity instance and a String value. You can acquire an identity instance for a persistent instance by using getObjectId( ), and you can access a persistent instance if you have an identity instance with getObjectById( ). Figure 10-2 shows these methods.
You can also convert an identity instance to a String by using toString( ). You can then use the returned String to reconstruct a corresponding identity instance with newObjectIdInstance( ). These capabilities are the reasons why you need to define toString( ) and a constructor that accepts a single String argument. Now let's describe the functionality of these methods in detail. These methods work for each identity type.
You can access the identity class of a persistent class by calling the following PersistenceManager method:
Class getObjectIdClass(Class persistentClass);
Passing the Class of a persistent class that uses datastore or nondurable identity returns the implementation-defined identity class. Passing the Class of a persistent class that uses application identity returns your application identity class. The method returns null if the parameter is null, the class referenced by persistentClass is abstract or not persistent, or the metadata specifies that the persistent class uses application identity and the implementation does not support application identity.
When using the JDO reference implementation, the following lines of code:
Class c1 = pm.getObjectIdClass(com.mediamania.store.Customer.class); System.out.println(c1.toString( )); Class c2 = pm.getObjectIdClass(com.mediamania.store.appid.Customer.class); System.out.println(c2.toString( ));
produce the following output:
class com.sun.jdori.fostore.OID class com.mediamania.store.appid.Customer$Id
JDO provides two methods to access the identity of a persistent instance. You can use either the PersistenceManager method:
Object getObjectId(Object obj);
or the JDOHelper method:
static Object getObjectId(Object obj);
These methods return null if the obj instance is transient, null, or not of a persistent class. Otherwise, they return an identity instance for the obj parameter. The identity instance returned is guaranteed to be unique only in the context of the PersistenceManager that created the identity and only for datastore and application identity. Within a transaction, the identity returned will be unique when compared with the identity of all the other persistent instances associated with the PersistenceManager, regardless of their type of identity.
There are only a small number of RentalCode instances in our example; this is reference data that rarely changes in the datastore. Suppose a MediaMania store application needs to establish references to RentalCode instances quickly. Here we deal specifically with the RentalCode class defined in the com.mediamania.store package. For example, consider the application that creates new MediaItem instances when the store receives new DVDs. The application wants to reference them by their code value. Instead of performing a query to access a specific RentalCode instance, the following utility class maintains a mapping from the code value to the RentalCode instance:
package com.mediamania.store; import java.util.Iterator; import java.util.HashMap; import javax.jdo.PersistenceManager; import javax.jdo.Extent; public class RentalCodeAccessor { private static HashMap rentalCodes; private static PersistenceManager pm; public static synchronized void initialize(PersistenceManager thePM) { pm = thePM; rentalCodes = new HashMap( ); Extent rentalCodeExtent = pm.getExtent(RentalCode.class, true); Iterator iter = rentalCodeExtent.iterator( ); while (iter.hasNext( )) { RentalCode rentalCode = (RentalCode) iter.next( ); Object id = pm.getObjectId(rentalCode); [1] rentalCodes.put(rentalCode.getCode( ), id); } rentalCodeExtent.close(iter); } public static Object getId(String code) { [2] return rentalCodes.get(code); } }
The class has a static initialize( ) method that is called to read the RentalCode instances from the datastore and populate a Map, where the key of an entry is the code value of a RentalCode, and the entry's value is the identity of the RentalCode instance. We acquire the identity for a RentalCode instance on line [1] and place an entry into the Map on the next line. On line [2], we define getId( ), which returns the identity instance associated with a particular code value, or null if there is no entry for the provided code.
The application can then make calls to getId( ) to access identity instances:
Object id = RentalCodeAccessor.getId("Hot"); System.out.println(id.toString( )); id = RentalCodeAccessor.getId("Recent"); System.out.println(id.toString( )); id = RentalCodeAccessor.getId("Oldie"); System.out.println(id.toString( ));
When using the reference implementation, these lines of code produce the following output:
OID: 102-11 OID: 102-13 OID: 102-15
The RentalCode class defined in the com.mediamania.store package uses datastore identity. This output shows the reference implementation's representation of a datastore identity value. The String representation of datastore identity is different with each JDO implementation. The value 102 denotes a specific class (RentalCode) and the numbers 11, 13, and 15 identify specific instances.
The identity value returned by getObjectId( ) is the identity of the instance at the beginning of the transaction. Later in this chapter, we'll discuss the case where you can change the application identity of an instance during a transaction. In this situation, you use another method to return the current identity of an instance.
An identity instance does not necessarily contain any of the internal state of a persistent instance, nor is it necessarily an instance of the class the implementation uses internally to manage identity. The returned instance represents the identity for the application to use. Multiple identity instances obtained from the same PersistenceManager for the same persistent instance have the same identity value, and a call to equals( ) on two such instances returns true. The identity instances used as parameters or returned by getObjectId( ), getTransactionalObjectId( ), and getObjectById( ) are not saved internally; rather, they are copies of the implementation's internal representation, or they are used to find instances of the internal representation. Therefore, you can modify the instance returned by getObjectId( ); you will not affect the persistent instance or its identity.
The following PersistenceManager method attempts to find an instance in the cache with the specified identity:
Object getObjectById(Object oid, boolean validate);
The oid parameter is an identity instance that might have been returned by an earlier call to getObjectId( ) or getTransactionalObjectId( ), or it might be an application identity instance constructed by the application. We use the validate flag to tell the implementation whether or not it should verify that the instance associated with the oid identity parameter currently exists in the datastore.
We add the following method to the RentalCodeAccessor utility class:
public static RentalCode getRentalCode(String code) { Object id = rentalCodes.get(code); [1] if (id == null) return null; RentalCode rentalCode = (RentalCode) pm.getObjectById(id, true); [2] return rentalCode; }
On line [1], we look up the code value in the Map, returning null if it is not found. Otherwise, we call getObjectById( ) on line [2] to access the RentalCode instance associated with the identity value. RentalCodeAccessor provides access to RentalCode instances defined in the com.mediamania.store package, which use datastore identity. You should declare Object references to refer to instances of a vendor's datastore identity class.
Now let's look at an example of using getObjectById( ) to access instances that use application identity. In the com.mediamania.store.appid package we declared RentalCode and Customer persistent classes, with RentalCodeKey and Customer.Id identity classes, respectively. The following lines of code create instances of these application identity classes and access the associated instances:
RentalCodeKey key = new RentalCodeKey("High Demand"); RentalCode code = (RentalCode) pm.getObjectById(key, true); Customer.Id id = new Customer.Id("Brian", "Mathie", "330-555-2020"); Customer cust = (Customer) pm.getObjectById(id, true);
If the PersistenceManager cannot convert the oid parameter passed to getObjectById( ) to a valid identity instance, then it throws a JDOUserException. This could occur if the parameter is an instance of an application identity class and the implementation does not support application identity. Or, the instance may be of a class that is different from the one specified in the metadata.
If you pass a value of false for the validate parameter, the following behavior occurs:
If there is already an instance in the cache with the same identity as the oid parameter, the instance is returned. No change is made to the state of the returned instance.
If there is not already an instance in the cache with the same identity as the oid parameter, then an instance with the specified identity is created and returned.
If the instance does not exist in the datastore, this method may or may not fail. An implementation may immediately throw a JDODataStoreException, or it may return an instance. However, if it returns an instance, a subsequent access of its fields causes a JDODataStoreException to be thrown if the instance does not exist at that time. Further, if a relationship is established to this instance and the instance does not exist when the instance is flushed to the datastore, the transaction in which the association was made will fail.
The implementation decides whether to access the datastore, if required to determine the exact class of the persistent instance. This is the case with inheritance, where multiple persistent classes can share the same identity class.
If you pass true for the validate parameter, the following behavior occurs:
If a transactional instance is already in the cache with the same identity as the oid parameter, the instance is returned. The state of the returned instance is not changed.
If a nontransactional instance is in the cache with the same identity as the oid parameter, a transaction is active, and the instance exists in the datastore, a transactional instance is returned with a state consistent with the datastore.
If an instance with the same identity as the oid parameter is not in the cache but it does exist in the datastore, an instance with the specified identity is created and returned.
If an instance is already in the cache with the same identity as the oid parameter, the instance is not transactional, and the instance does not exist in the datastore, then a JDOObjectNotFoundException is thrown.
If an instance with the same identity as the oid parameter is not in the cache and it does not exist in the datastore, then a JDOObjectNotFoundException is thrown.
No change is made to the status of a transaction if JDOObjectNotFoundException is thrown. You will never get this exception as a result of executing a query. You can retrieve the failed instance by calling the exception's getFailedObject( ) method. Of course, the fields of the failed instance will not be initialized, since the instance does not exist in the datastore. But you can access the identity of the instance by calling getObjectId( ), which may be useful to debug the application.
All calls to getObjectById( ) with the same identity value and the same PersistenceManager instance return the same instance with the same Java identity (assuming the instances were not garbage-collected between calls). So, the following code outputs "same instance" to the output stream:
RentalCodeKey key = new RentalCodeKey("High Demand"); RentalCode code = (RentalCode) pm.getObjectById(key, true); RentalCodeKey key2 = new RentalCodeKey("High Demand"); RentalCode code2 = (RentalCode) pm.getObjectById(key2, true); if (code == code2) System.out.println("same instance");
Suppose we use different PersistenceManager instances (from the same PersistenceManagerFactory) in calls to getObjectById( ) with the same identity value. The instances returned will represent the same persistent instance, but they will have a different Java identity, because each PersistenceManager manages its own copy of persistent instances.
If you change the value of a primary-key field during a transaction, this action constitutes an attempt to change the identity of the instance. Changing the identity of an instance is supported only for application identity, and it is an optional JDO feature. The javax.jdo.option.ChangeApplicationIdentity option property indicates whether an implementation supports this feature. If it is not supported, the implementation throws a JDOUnsupportedOptionException whenever you attempt to change a primary-key field. Since this feature is optional, your application is more portable if it never changes a primary-key field.
For implementations that support the changing of an application identity, the implementation detects changes to primary-key fields. Changing the value of a primary-key field changes the identity value. The new identity value is either unique or already in use by another instance. If another persistent instance already has the identity value, a JDOUserException is thrown and the statement that attempted to change the field does not complete. If the resulting identity is unique, it is associated with the instance immediately upon completion of the statement that changed the primary-key field. If the transaction commits successfully, the existing instance in the datastore is updated with the values of any primary-key fields that have changed.
You need to take into account the fact that a change to the value of a primary-key field changes the identity of an instance in the datastore. This might result in a loss of integrity in a production environment that keeps an audit trail of all changes, as the historical record of all changes would not reflect the current identity of the instance in the datastore. In these environments it is best if you do not change the value of a primary-key field.
The PersistenceManager method getObjectId( ) returns the identity of an instance as of the beginning of a transaction. If the application changes the identity of an instance during a transaction, getObjectId( ) continues to return the identity as of the beginning of the transaction until afterCompletion( ) has been called, at which point it returns a different identity value if the transaction commits successfully. Chapter 7 describes the afterCompletion( ) method of the Synchronization interface.
The PersistenceManager method:
Object getTransactionalObjectId(Object obj);
and the JDOHelper method:
static Object getTransactionalObjectId(Object obj);
return the current identity of an instance, taking into account any changes that may have been made to primary-key fields. These methods return null if the instance is transient, null, or not of a persistent class. If no transaction is in progress or if none of the primary-key fields have been modified, then these methods have the same behavior as getObjectId( ).
The getObjectId( ) method returns an identity instance, declared to be of type Object. You can call toString( ) on the identity instance to obtain a String representation of the identity value. This String can be written to a file or passed to some other software outside the current JVM context. If the persistent class has application identity, the toString( ) you defined for the application identity class will determine the form of the String's value. If the persistent class uses datastore or nondurable identity, the String value is implementation-specific.
You can later use the String value to construct an identity instance. The following PersistenceManager method returns an identity instance, given the Class and String parameters:
Object newObjectIdInstance(Class persistentClass, String str);
The str parameter should be the result of a previous call to toString( ) on an identity instance. The persistentClass parameter specifies the class of the instance identified by the str parameter. The newObjectIdInstance( ) method calls the identity class's public constructor that takes a String argument to initialize the identity instance.
In some development projects, we have passed the String representation of identity to an HTML screen to serve as a handle for referencing a persistent object in the browser's separate process context. The string representation of the identity value can be kept in a hidden element in the HTML. Each persistent instance rendered in the user interface can have its associated identity value. Then, when some user action in the browser requires an action to be performed on the instance in the cache, you can pass the identity string back to the application and use newObjectIdInstance( ) and getObjectById( ) to access the instance in the cache quickly.