The following six interfaces provide the basic set of JDO APIs that most applications need to use:
For each interface described in the following sections, you find a description of its purpose, followed by an overview of each method, and then examples that show how to use those methods in practice. 5.3.1 javax.jdo.PersistenceManagerFactoryThe PersistenceManagerFactory interface is typically the starting point for any JDO application; from it, an application gets instances of PersistenceManager . The PersistenceManagerFactory has a number of standard properties that can be configured; in addition, vendors can add their own properties to their PersistenceManagerFactory implementations , although all properties should follow the JavaBean pattern of setters and getters. PersistenceManagerFactory is responsible for managing the physical connections to the underlying datastore and may or may not implement connection pooling. In addition, a PersistenceManagerFactory can be used by both managed and non-managed applications. For non-managed applications, connection- related properties are used to indicate how to make a connection to the datastore. For managed applications, connection management is delegated to an external connection factory. Chapter 7 provides more details on managed and non-managed applications. Simply put, a managed application typically runs within a J2EE container; a non-managed application is a standalone Java application. A non-managed application creates its own connections to the datastore and explicitly manages its own transactions. A managed application gets connections from a connection factory and may delegate transaction management to the container within which it runs. 5.3.1.1 Creating a PersistenceManagerFactoryThere are three main ways to create a PersistenceManagerFactory :
A PersistenceManagerFactory instance created by either a JNDI lookup or by JDOHelper is not serializable in order to avoid a security hole. Because a PersistenceManagerFactory can potentially contain a password, if it were serializable, it could expose this password. 5.3.1.2 PersistenceManagerFactory PropertiesThe standard properties defined by JDO for PersistenceManagerFactory are split between those that control the creation of connections to the underlying datastore (such as ConnectionURL and ConnectionUserName ) and those that configure the default behavior of PersistenceManager instances (such as IgnoreCache and Multithreaded ). Depending on whether the application is managed or non-managed, different connection-based properties apply. Table 5-1 lists the non-managed connection properties. Table 5-1. Non-Managed Connection Properties
The values of these properties are dependent on the underlying datastore being used, but not all may be required or even relevant. The documentation for a given JDO implementation should identify which of these properties are used and what values they should be given. For managed applications, the PersistenceManagerFactory uses an external connection factory to create datastore connections. If any of the connection factory properties are set (non-null), then the un-managed properties above are effectively ignored and the external connection factory is used instead. Typically, a managed application would not explicitly create or configure a PersistenceManagerFactory ; it would look one up via JNDI instead. A PersistenceManagerFactory instance would be initialized and registered with JNDI beforehand (although exactly how this is done depends on the managed environment being used and the JDO implementation). Table 5-2 lists the managed connection properties. Table 5-2. Managed Connection Properties
Table 5-3 lists the configuration properties that provide default values for each PersistenceManager instance created by a given PersistenceManagerFactory . Table 5-3. Configuration Properties
5.3.1.3 Method summaryIn addition to the methods to get and set properties, the PersistenceManagerFactory has five other methods:
Table 5-4 lists the strings used to denote support for the standard JDO optional features and query language: Table 5-4. Optional Features
5.3.1.4 Usage guidelinesA non-managed application can create a PersistenceManagerFactory via JNDI, JDOHelper , or explicitly using a vendor-supplied constructor. If using JNDI, a PersistenceManagerFactory needs to be initially created via an implementation-specific constructor and bound to its JNDI name. When the application looks up the PersistenceManagerFactory via JNDI, it gets a de-serialized instance. Typically, a PersistenceManagerFactory instance is shared within an application, in which case a singleton pattern should be used to manage the lookup and sharing of the PersistenceManagerFactory . The following code snippet from PMFSingletonFromJNDIExample.java shows how a PersistenceManagerFactory can be created and registered with JNDI, and then looked and managed as a singleton instance: import javax.naming.*; import javax.jdo.*; public class PMFSingletonFromJNDIExample { // JNDI name for the PersistenceManagerFactory instance private static String name = PMFSingletonFromJNDIExample.class.getName(); // PersistenceManagerFactory Singleton private static PersistenceManagerFactory pmf; /* * This method returns the singleton * PersistenceManagerFactory. If required, it first * initializes the singleton by looking it up via JNDI. * If it can't lookup the PersistenceManagerFactory via * JNDI, it throws JDOFatalUserException */ public static PersistenceManagerFactory getPersistenceManagerFactory() { if (pmf == null) { try { InitialContext context = new InitialContext(); pmf = (PersistenceManagerFactory) context.lookup(name); } catch (NamingException e) { throw new JDOFatalUserException( "Can't lookup PersistenceManagerFactory: " + name, e); } } return pmf; } /* * This class can be run to create a * PersistenceManagerFactory instance * and register it with JNDI. * The PersistenceManagerFactoryClass property * would need to be changed from "XXX". */ public static void main(String[] args) { if (args.length != 3) { System.out.println( "Invalid arguments, use: <url> <name> <password>"); System.exit(-1); } Properties properties = new Properties(); properties.put( "javax.jdo.PersistenceManagerFactoryClass", "XXX"); properties.put( "javax.jdo.option.ConnectionURL", args[0]); properties.put( "javax.jdo.option.ConnectionUserName", args[1]); properties.put( "javax.jdo.option.ConnectionPassword", args[2]); PersistenceManagerFactory pmf = JDOHelper.getPersistenceManagerFactory(properties); try { InitialContext context = new InitialContext(); context.bind(name, pmf); } catch (NamingException e) { System.out.println("Can't bind PersistenceManager"); e.printStackTrace(); } } } If not using JNDI, the singleton needs to be created and initialized directly. JDOHelper can be used to do this based on a set of properties. The following code snippet taken from PMFSingletonExample.java shows how this could work. It uses an initialize() method that creates the PersistenceManagerFactory singleton from the specified properties: import java.util.Properties; import javax.jdo.*; public class PMFSingletonExample { // PersistenceManagerFactory singleton private static PersistenceManagerFactory pmf; /* * This method returns the singleton * PersistenceManagerFactory. * If it the singleton hasn't been initialized, it throws * JDOFatalUserException. */ public static PersistenceManagerFactory getPersistenceManagerFactory() { if (pmf == null) { throw new JDOUserException( "PersistenceManagerFactory not initialized."); } return pmf; } /* * Creates a PersistenceManagerFactory based on the * specified properties and initializes the singleton. * The actual PersistenceManagerFactoryClass needs to * be specified rather than "XXX". */ public static void intialize( String url, String name, String password) { Properties properties = new Properties(); properties.put( "javax.jdo.PersistenceManagerFactoryClass", "XXX"); properties.put( "javax.jdo.option.ConnectionURL", url); properties.put( "javax.jdo.option.ConnectionUserName", name); properties.put( "javax.jdo.option.ConnectionPassword", password); pmf = JDOHelper.getPersistenceManagerFactory(properties); } } Of course, before this example can be used, the "XXX" strings would need to be replaced with values appropriate to the JDO implementation being used. The previous example has the disadvantage that the initialize() method needs to be explicitly called with the appropriate connection properties. An alternative to this is shown in the following code snippet taken from PMFSingletonFromFileExample.java . It reads the properties from a file whose name is specified using a system property: import java.util.Properties; import java.io.*; import javax.jdo.*; public class PMFSingletonFromFileExample { // PersistenceManagerFactory singleton private static PersistenceManagerFactory pmf; /* * This method returns the singleton * PersistenceManagerFactory. If the singleton * hasn't been initialized, it creates a * PersistenceManagerFactory from a properties * file denoted by the system property "jdo.properties" * and initializes the singleton. */ public static PersistenceManagerFactory getPersistenceManagerFactory() { if (pmf == null) { String filename = System.getProperty("jdo.properties"); if (filename == null) { throw new JDOFatalUserException( "System property 'jdo.properties' not defined"); } else { Properties properties = new Properties(); try { properties.load(new FileInputStream(filename)); pmf = JDOHelper. getPersistenceManagerFactory(properties); } catch (java.io.IOException e) { throw new JDOFatalUserException( "Error reading '" + filename + "'", e); } } } return pmf; } } The following code snippet taken from SupportedOptionsExample.java shows how to print out the options that a PersistenceManagerFactory instance supports: Iterator iter = pmf.supportedOptions().iterator(); System.out.println( "PersistenceManagerFactory class '" + pmf.getClass().getName() + "' supports:"); while (iter.hasNext()) { String option = (String) iter.next(); System.out.println(" " + option); } Using the getPersistenceManager() methods is explained in the section on PersistenceManager. 5.3.2 PersistenceManagerPersistenceManager is the primary interface that a JDO application would use. It has a number of standard properties that govern how it manages persistent objects in memory and provides methods for an application to interact with those persistent objects and the underlying datastore. It also acts as a factory for Query , Extent , and Transaction instances. 5.3.2.1 Creating a PersistenceManagerThere are two main ways to create a PersistenceManager :
5.3.2.2 PersistenceManager propertiesTable 5-5 lists the three properties defined by PersistenceManager . Table 5-5. PersistenceManager Properties
IgnoreCache Normally, the result from a Query or Extent reflects any in-memory changes made within the current transaction. If a new persistent object was created or a persistent object was modified or deleted, any result would reflect these changes. Generally , this is the desired behavior. However, it may come at a cost or be undesirable in certain situations. Depending on the underlying datastore, it might be necessary to synchronize any in-memory changes with the datastore prior to executing the Query or iterating through the Extent . As an optimization, an application can choose not to incur this overhead, in which case the IgnoreCache property should be set to true . A result from a Query or iterating through an Extent may then not reflect in-memory changes made within the current transaction. If a new instance is created that matches a given query, it may not be part of the returned result. The advantage is that executing the query no longer requires in-memory changes to be synchronized with the datastore beforehand. This property is just a hint; even if set, it may be ignored by a JDO implementation. An application can't rely on a Query to ignore in-memory changes just because this property is true . Unless there is a specific performance requirement, an application typically shouldn't set this property to true . Multithreaded It is safe for an application to use multiple threads with JDO as long as there is only one thread using a given PersistenceManager or instances created by a PersistenceManager ( Query , Extent , PersistenceCapable instances, and so on) at a given time. Simply put, if each thread has its own PersistenceManager , there is no problem. However, if the application requires multiple threads to be able to use the same PersistenceManager instance at the same time, this property should be set to true . If this property is true , the PersistenceManager synchronizes internally to ensure that internal data structures do not get corrupted when multiple threads use the PersistenceManager instance concurrently. Synchronization may incur additional overhead and adversely affect performance; this property should be set only if multi-threaded access is really required. See Chapter 7 for more details on developing multithreaded applications. 5.3.2.3 Method summaryPersistenceManager has methods that allow an application to interact with persistent objects and the underlying datastore, and to get Query , Extent , and Transaction instances. There are many methods, but these are the key ones that a JDO application is likely to use: Transaction currentTransaction() The PersistenceManager methods can be grouped into the following categories:
Connection Management Methods These methods allow the application to indicate that it no longer needs the PersistenceManager instance or allows it to get the associated PersistenceManagerFactory . void close() Cache Management Methods Typically, an application does not need to explicitly manage persistent objects in-memory; the JDO runtime implicitly takes care of them. In some situations, the application may need to explicitly release, retrieve, or refresh a persistent object or group of persistent objects. A common reason for this is as a performance optimization: An application can explicitly retrieve a collection of persistent objects from the datastore into memory in one go, rather than retrieving them implicitly one by one. The methods below all follow the same set of conventions. There is a base method that takes a single Object argument. Then there are additional methods whose names are post-fixed with "All" to indicate that they take multiple objects (either an Object[] or Collection argument). There may also be an "All" method that takes no argument, in which case it operates on all relevant in-memory instances. If null is used as an argument to a method that takes Object , then the null is ignored and the method is effectively a no-op. If null is passed to a method that takes an Object[] or Collection , then NullPointerException is thrown. If the Object[] or Collection argument itself contains null elements, then these are ignored. void evict(Object obj) void evictAll(Object[] objs) void evictAll(Collection objs) void evictAll()
void refresh (Object obj) void refreshAll (Object[] objs) void refreshAll (Collection objs) void refreshAll() Instance Management Methods These methods allow an application to interact with the PersistenceManager and PersistenceCapable instances that it manages: Object getObjectId (Object obj) Object getTransactionalObjectId (Object obj)
Class getObjectIdClass (Class pcClass)
void makeTransient (Object obj) void makeTransientAll (Object[] objs) void makeTransientAll (Collection objs) Factory Methods PersistenceMananger acts as a factory for Query , Extent , and Transaction instances. The following factory methods can be used to construct these instances: Query newQuery() Query newQuery (Object query) Query newQuery (String language, Object query) Query newQuery (Class cls) Query newQuery (Extent extent) Query newQuery (Class cls, Collection cln) Query newQuery (Class cls, String filter) Query newQuery (Class cls, Collection cln, String filter) Query newQuery (Extent extent, String filter) 5.3.2.4 Usage guidelinesThe following examples demonstrate how to use the various APIs defined on PersistenceManager : Connection Management Methods Getting a PersistenceManager instance is straightforward; however, for a server-side application, care needs to be taken to ensure that the PersistenceManager instance is always closed. The easiest way to do this is to wrap the use of a PersistenceManager instance within a try block and use a finally block to ensure that the PersistenceManager instance gets closed, even if an exception is thrown. The finally block can also ensure that any active transaction is first rolled back before the PersistenceManager instance is closed. The following code snippet taken from PersistenceManagerExample.java shows how to use try and finally to ensure that a PersistenceManager instance is closed: PersistenceManager pm = null; try { pm = pmf.getPersistenceManager(); // Do something interesting pm.close(); } finally { if (pm != null && !pm.isClosed()) { if (pm.currentTransaction().isActive()) { pm.currentTransaction().rollback(); } pm.close(); } } Transactions The currentTransaction() method returns the Transaction instance for the PersistenceManager instance. For a non-mananged application, the Transaction instance can be used to begin and end transactions. The following code snippet taken from TransactionExample.java shows how to begin and commit a transaction: Transaction tx = pm.currentTransaction(); tx.begin(); // Do something interesting tx.commit(); Extents The getExtent() method can be used to get a collection of all the instances of a given persistence-capable class (and optionally subclasses) in the datastore. The following code snippet taken from ExtentExample.java shows how to iterate through all instances of the Book class (and subclasses): Extent extent = pm.getExtent(Book.class, true); Iterator books = extent.iterator(); System.out.println("Listing of all books:"); while (books.hasNext()) { Book book = (Book) books.next(); System.out.println(" " + book.getTitle()); } extent.close(books); Evict Methods The previous example retrieved all instances of the Book class; however, if a large number of books are in the datastore, then they may not all fit into memory at the same time. The evict methods can be useful when an application needs to access a large number of persistent objects within a single transaction, but not all instances can possibly fit in memory. The following code snippet taken from EvictExample.java shows how to use the evict() method to evict each Book instance after it has been accessed: Extent extent = pm.getExtent(Book.class, true); Iterator books = extent.iterator(); System.out.println("Listing of all books:"); while (books.hasNext()) { Book book = (Book) books.next(); System.out.println(" " + book.getTitle()); pm.evict(book); } extent.close(books); Retrieve Methods The previous example retrieved each persistent object one at a time from the datastore. As an optimization, the retrieveAll() method can be used to potentially retrieve multiple persistent objects from the datastore in one go. This can improve the performance of the application by reducing the number of times the underlying datastore needs to be accessed. The following code snippet taken from RetrieveExample.java shows how the retrieveAll() method can be used to retrieve a group of instances: Extent extent = pm.getExtent(Book.class, true); Iterator books = extent.iterator(); System.out.println("Listing of all books:"); List list = new ArrayList(1024); while (books.hasNext()) { list.add(iter.next()); if (list.size() == 1024 !books.hasNext()) { pm.retrieveAll(list); for (int i = 0; i < list.size(); ++i) { Book book = (Book) list.get(i); System.out.println(" " + book.getTitle()); } pm.evictAll(list); list.clear(); } } extent.close(books); If supported, a further optimization would be to use a retrieveAll() method that takes a Boolean flag to indicate that only the fields in the default fetch group are required. These methods were introduced in the 1.0.1 maintenance revision, so they may not be supported by all JDO implementations initially. Timing the two examples EvictExample.java and RetrieveExample.java using the same dataset shows how retrieving objects can dramatically improve the performance of an application. Refresh Methods These methods are primarily useful when using an optimistic transaction or using non-transactional instances to re-retrieve the fields of an instance from the datastore. However, another use is as a way to undo an in-memory modification to a persistent object within a transaction without rolling back the entire transaction. If an application decides that a change should not have been made to a particular instance, it is possible to use refresh() to re-retrieve the instance's fields from the datastore and thus overwrite any modified fields. The following code snippet taken from RefreshExample.java shows how retrieve() can be used to undo a change made to a book's title: tx.begin(); Author author = new Author("Keiron McCammon"); pm.makePersistent(author); tx.commit(); tx.begin(); System.out.println( "Author's name is '" + author.getName() + "'."); author.setName("Sameer Tyagi"); System.out.println( "Author's name changed to '" + author.getName() + "'."); pm.refresh(author); System.out.println( "Author's name after refresh is '" + author.getName() + "'."); tx.commit(); The output would be as follows : Author's name is 'Keiron McCammon'. Author's name changed to 'Sameer Tyagi'. Author's name after refresh is 'Keiron McCammon'. Object Identity Methods A persistence object's identity is represented by an instance of a normal Java class. An object identity instance is a useful way to pass a reference to a persistent object between PersistenceManager instances and even between PersistenceManager instances in different JVMs (because it is serializable). The following code snippet taken from ObjectIdentityExample.java shows how to get the identity of a persistent object and use it to create a reference to the persistent object in a different PersistenceManager instance: PersistenceManager pm = pmf.getPersistenceManager(); Transaction tx = pm.currentTransaction(); tx.begin(); Author author = new Author("Keiron McCammon"); pm.makePersistent(author); tx.commit(); Object oid = pm.getObjectId(author); System.out.println("Author's object identity is: " + oid); pm.close(); PersistenceManager pm2 = pmf.getPersistenceManager(); Transaction tx2 = pm2.currentTransaction(); tx2.begin(); Author author2 = (Author) pm.getObjectById(oid, true); System.out.println("Author is: " + author2.getName()); tx2.commit(); pm2.close(); Although the above example shows how to use a persistent object's identity to re-create a reference to it in a different PersistenceManager instance within the same JVM, it could easily have been passed via RMI to a different JVM altogether. In this case, the object identity instance just gets serialized and passed over the wire. In some situations, it is desirable to pass the identity of a persistent object to a non-JDO application. This application would then use the identity as a parameter in additional requests (a Web browser, for example). In this situation, the object identity instance can be converted to a string representation. The following code snippet taken from StringObjectIdentityExample.java shows how to convert a persistent object's identity to a string and re-create a reference to the persistent object again from the string: Transaction tx = pm.currentTransaction(); tx.begin(); Author author = new Author("Keiron McCammon"); pm.makePersistent(author); tx.commit(); String oid = pm.getObjectId(author).toString(); System.out.println("Author's object identity is: " + oid); pm.close(); PersistenceManager pm2 = pmf.getPersistenceManager(); Transaction tx2 = pm2.currentTransaction(); tx2.begin(); Author author2 = (Author) pm.getObjectById( pm.newObjectIdInstance(Author.class, oid), true); System.out.println("Author is: " + author2.getName()); tx2.commit(); The toString() method is used to convert the persistent object's identity to a string. In a different PersistenceManager instance, newObjectIdInstance() is used to re-create an object identity instance from the string; this is then used to get a reference to the original persistent object. Make and Delete Persistent Methods The makePersistent() and deletePersistent() methods create and delete persistent objects in the underlying datastore. The makePersistent() methods mark in-memory transient instances of persistence-capable classes as persistent. The methods can be called at any point within a transaction, and because of persistence by reachability, only the root of a graph of interconnected instances needs to be made persistent. The following code snippet taken from MakePersistentExample.java creates instances of Author, Address, Book, and Publisher and associates them together. Because an Author references an Address and a Book, and a Book references a Publisher, only the Author instance needs to be made persistent. However, it wouldn't matter if the application also made the Address, Book and Publisher instances explicitly persistent (which might be the case if there is doubt about what is reachable from where): tx.begin(); Author author = new Author( "Keiron McCammon", new Address( "6539 Dumbarton Circle", "Fremont", "CA", "94555")); Publisher publisher = new Publisher("Sun Microsystems Press"); Book book = new Book("Core Java Data Objects", "0-13-140731-7"); author.addBook(book); publisher.addBook(book); pm.makePersistent(author); tx.commit(); The deletePersistent() methods mark in-memory persistent objects as deleted. The methods can be called at any point during a transaction, but unlike makePersistent() , they affect only the specific persistent object being deleted. It is the responsibility of the application to delete any referenced persistent objects that are no longer required. The following code snippet taken from DeletePersistentExample.java gets the first Author instance from the class Extent , deletes the associated Address instance, and removes each Book from the Author: Iterator authors = extent.iterator(); if (authors.hasNext()) { Author author = (Author) authors.next(); pm.deletePersistent(author.getAddress()); Iterator books = author.getAllBooks(); while (books.hasNext()) { Book book = (Book) books.next(); author.removeBook(book); } pm.deletePersistent(author); } Rather than relying on the application to remember to tidy up every time it deletes a persistent object, a persistence-capable class can implement the InstanceCallbacks interface and use the jdoPreDelete() method to tidy up automatically upon deletion. See the section on InstanceCallbacks for more details. Make Transient Methods An in-memory persistent object can be made transient using the makeTransient() methods. After it is made transient, the in-memory instance has no object identity and is no longer associated with a PersistenceManager . Because the fields of the instance are left as-is, the application should first use one of the retrieve() methods to ensure that all the fields of the instance have been retrieved from the datastore. The following code snippet taken from MakeTransientExample.java first creates an Author instance in one transaction, and then in another retrieves all its fields and makes it transient. Even after the PersistenceManager instance is closed, the transient Author instance can still be used: PersistenceManager pm = pmf.getPersistenceManager(); Transaction tx = pm.currentTransaction(); tx.begin(); Author author = new Author("Keiron McCammon"); pm.makePersistent(author); tx.commit(); tx.begin(); pm.retrieve(author); pm.makeTransient(author); tx.commit(); pm.close(); System.out.println( "Author's name is: " + author.getName()); The makeTransient() methods affect only the specific instance being made transient. If referenced persistent objects should also be made transient, it is the application's responsibility to make each instance explicitly transient.
Transactional and Non-Transactional Methods The behavior of these methods depends on whether a transient or persistent instance is used. The following code snippet taken from TransientTransactionalExample.java shows how a transient instance of a persistence-capable class can be made transactional so that, on rollback, the instance's fields revert to their values at the beginning of the transaction and how, when made non-transactional again, the fields are left as-is on rollback: Author author = new Author("Keiron McCammon"); System.out.println("Making instance transactional."); pm.makeTransactional(author); Transaction tx = pm.currentTransaction(); tx.begin(); System.out.println( "Author's name is '" + author.getName() + "'"); author.setName("Sameer Tyagi"); System.out.println( "Modified name is '" + author.getName() + "'"); tx.rollback(); System.out.println( "Name after rollback is '" + author.getName() + "'"); System.out.println("Making instance non-transactional."); pm.makeNontransactional(author); tx.begin(); System.out.println("Name is '" + author.getName() + "'"); author.setName("Sameer Tyagi"); System.out.println( "Modified name is '" + author.getName() + "'"); tx.rollback(); System.out.println( "Name after rollback is '" + author.getName() + "'"); The output would be as follows: Making instance transactional. Author's name is 'Keiron McCammon'. Modified name is 'Sameer Tyagi'. Name after rollback is 'Keiron McCammon'. Making instance non-transactional. Name is 'Keiron McCammon'. Modified name is 'Sameer Tyagi'. Name after rollback is 'Sameer Tyagi'. The following code snippet taken from PersistentTransactionalExample.java shows how a persistent object can be made non-transactional and accessed outside of the transaction and even modified. It then shows how, when the instance is made transactional again, any modified fields are simply discarded: tx.begin(); Author author = new Author("Keiron McCammon"); pm.makePersistent(author); tx.commit(); System.out.println("Making instance non-transactional."); pm.makeNontransactional(author); System.out.println( "Author's name is '" + author.getName() + "'"); author.setName("Sameer Tyagi"); System.out.println( "Modified name is '" + author.getName() + "'"); tx.begin(); System.out.println("Making instance transactional."); pm.makeTransactional(author); System.out.println( "Transactional name is '" + author.getName() + "'"); tx.commit(); The output would be as follows: Making instance non-transactional. Author's name is 'Keiron McCammon'. Modified name is 'Sameer Tyagi'. Making instance transactional. Transactional name is 'Keiron McCammon'. 5.3.3 ExtentAn Extent instance essentially represents a collection of all the persistent objects of a persistence-capable class in the datastore. An Extent can be used to iterate through all the persistent objects of the class in no particular order or used to construct a Query . By default, the JDO runtime in conjunction with the underlying datastore maintains an Extent for every persistence-capable class. Some datastores don't implicitly maintain a collection of all persistent objects being stored per class, in which case a collection of instances must be explicitly maintained instead. This can add overhead. If the application has no need to get an Extent or Query the instances of a class, then it can avoid any unnecessary overhead by setting the requires-extent attribute to false in the metadata for the class. For many datastores, Extent management is implicit (for example, a relational database always knows all the rows in a table). In this case, setting the requires-extent attribute to false makes no difference. The Extent instance itself doesn't actually reference persistent objects; rather, it just maintains a reference to a persistence-capable class, a subclasses flag, and a collection of open Iterators . It's the Iterators created by the Extent or Query instances created using the Extent that return the persistent objects. Because the instances of persistence-capable classes can vary over time as different applications create and delete persistent objects, Iterators created by an Extent at different points may return different sets of persistent objects. An Iterator returns the persistent objects in the datastore at the time the Iterator is created. Depending on the IgnoreCache property of the PersistenceManager , the Iterator may or may include instances made persistent or deleted within the current transaction. 5.3.3.1 Creating an ExtentAn Extent is created using the getExtent() method on PersistenceManager : public Extent getExtent(Class cls, Boolean subclasses) The boolean flag controls whether the Extent contains persistent objects of just the specified class or includes persistent objects of all subclasses also. 5.3.3.2 Extent propertiesExtent defines three read-only properties that are set when the Extent is created, as shown in Table 5-6. Table 5-6. Extent Properties
5.3.3.3 Method summaryExtent provides a method to get an Iterator (that can then be used to iterate through all the persistent objects represented by the Extent ) and methods to close iterators after they have been used. void close (Iterator iter)
5.3.3.4 Usage guidelinesThe following code snippet taken from ExtentExample.java shows how to iterate through all instances of the Book class, closing the Iterator when finished: Extent extent = pm.getExtent(Book.class, true); Iterator books = extent.iterator(); System.out.println("Listing of all books:"); while (books.hasNext()) { Book book = (Book) books.next(); System.out.println(" " + book.getTitle()); } extent.close(books); 5.3.4 QueryJDO defines both a query API and a query language. The API allows an application to construct and execute a query programmatically, and the query language allows an application to specify the persistent objects that it wants. The query language is called JDOQL (JDO Query Language), and its syntax is a Java Boolean expression. Essentially, a JDO application uses a JDOQL string (referred to as a filter) to create a Query instance; this instance is then executed and returns a collection of persistent objects that satisfy the specified filter. As well as its own query language, JDO allows implementations to offer additional query languages. An application would use the same query APIs defined by JDO; however, the syntax of the query string would instead be SQL, OQL, or some other proprietary language. JDO mandates support for JDOQL, but leaves support for additional languages as optional. An implementation that supports additional query languages would include them in the list of supported options returned from the supportedOptions() method on PersistenceManager . A Query consists of a set of candidate instances, which can be specified using a Java class, an Extent , or a Java collection, and a filter string. In addition, it is possible to also declare import statements, parameters, and variables , as well as an ordering for the set of results. When executed, a Query takes the set of candidate instances and returns a Java collection of references to the instances that satisfy the query filter. The goal of JDOQL is to provide a simple query grammar that is familiar to Java programmers and that can be executed by the JDO implementation, possibly by converting it to a different representation and passing it to the underlying data-store. JDO makes no assumptions about where the query is actually executed. Depending on the JDO implementation and the underlying datastore being used, a query may be executed be retrieving all the persistent objects into memory, or it may simply be passed to the underlying datastore. See Chapter 6 for more details on the JDOQL syntax. 5.3.4.1 Creating a QueryA Query is created using the newQuery() methods on PersistenceManager . There are essentially five ways to create a Query :
The set of candidate instances for a Query should be specified one way, using a Java class, an Extent , or a Java collection. Query newQuery() 5.3.4.2 Query propertiesQuery defines two properties, as shown in Table 5-7. Table 5-7. Query Properties
5.3.4.3 Method summaryThe Query methods can be grouped into the following categories:
Query Definition Methods The following methods are used to define a Query . Before a Query can be executed, a set of candidate instances and a filter have to be set as a minimum. void declareImports (String imports) Query Execution Methods The following methods are used to execute a Query and get a collection of persistent objects that satisfy the filter. void close (Object result)
5.3.4.4 Usage guidelinesChapter 6 provides greater details on the JDOQL syntax and how to use queries within a JDO application. The following examples serve to provide an introduction to the basics. Finding a persistent object by a field value requires only a simple query. The following code snippet taken from SimpleQueryExample.java shows how to create and execute a simple query to find all instances of Book with a given title. The Book's title is hard coded into the filter string used to create the Query: Query query = pm.newQuery( Book.class, "title == \"Core Java Data Objects\""); Collection result = (Collection) query.execute(); Iterator iter = result.iterator(); while (iter.hasNext()) { Book book = (Book) iter.next(); System.out.println( book.getTitle() + " - " + book.getISBN()); } query.close(result); Rather than hard coding a literal in the filter string, a parameter can be used instead. The following code snippet taken from SimpleQueryWithParameterExample.java is the same as the previous example, except that it defines a parameter to specify the Book's title rather than hard coding it. The this keyword is used to resolve the ambiguity between the class's field name and the parameter's name (both of which are called title ): Query query = pm.newQuery(Book.class, "this.title == title"); query.declareParameters("String title"); Collection result = (Collection) query.execute("Core Java Data Objects"); Iterator iter = result.iterator(); while (iter.hasNext()) { Book book = (Book) iter.next(); System.out.println( book.getTitle() + " - " + book.getISBN()); } query.close(result); The results from a query are not returned in any particular order by default. To specify an order, use the setOrdering() method. The following code snippet taken from SimpleQueryWithOrderingExample.java extends the previous example, but specifies that the returned Book instances should be ordered by their ISBN numbers : Query query = pm.newQuery(Book.class, "this.title == title"); query.declareParameters("String title"); query.setOrdering("isbn ascending"); Collection result = (Collection) query.execute("Core Java Data Objects"); Iterator iter = result.iterator(); while (iter.hasNext()) { Book book = (Book) iter.next(); System.out.println( book.getTitle() + " - " + book.getISBN()); } query.close(result); A Query can also use navigation to specify fields of referenced instances. The following code snippet taken from SimpleQueryWithNavigationExample.java shows how to use the " . " notation to navigate to fields of a referenced instance. The query returns all books published by a given publisher, using the " . " notation to check the name field of the Publisher instance referenced by the publisher field for each book: Query query = pm.newQuery(Book.class, "publisher.name == name"); query.declareParameters("String name"); Collection result = (Collection) query.execute("Sun Microsystems Press"); Iterator iter = result.iterator(); while (iter.hasNext()) { Book book = (Book) iter.next(); System.out.println( book.getTitle() + " - " + book.getISBN()); } query.close(result); A Query can also navigate to multiple persistent objects contained in a collection and check the fields of those contained persistent objects. If there is at least one persistent object in the collection that matches, then the referencing persistent object is returned. The following code snippet taken from SimpleQueryUsingContainsExample.java shows how to use contains() and a variable to navigate to fields of persistent objects contained in a collection. The query returns all Books by a given Author. Essentially, for each Book, the Authors contained in the authors collection are checked to see whether the name matches that given. If there is at least one Author contained in the collection with the given name, then the Book is returned: Query query = pm.newQuery( Book.class, "authors.contains(author) && author.name == name"); query.declareParameters("String name"); query.declareVariables("Author author"); Collection result = (Collection) query.execute("Keiron McCammon"); Iterator iter = result.iterator(); while (iter.hasNext()) { Book book = (Book) iter.next(); System.out.println( book.getTitle() + " - " + book.getISBN()); } query.close(result); See Chapter 6 for more details on using JDOQL. 5.3.5 TransactionThe Transaction interface provides the methods that allow an application to control the underlying datastore transactions. Each PersistenceManager has an associated Transaction instance. A Transaction is initially inactive, becomes active after begin() , and remains active until either commit() or rollback() (or JDOFatalDataStoreException is thrown), at which point it becomes inactive again. 5.3.5.1 Creating a TransactionThe currentTransaction() method defined on PersistenceManager can be used to get the Transaction instance associated with a PersistenceManager ; each PersistenceManager has one Transaction instance: public Transaction currentTransaction() 5.3.5.2 Transaction propertiesTransaction defines a number of properties that control various semantics of a transaction. Once set, the properties of a Transaction remain so until changed again (they are not affected by transaction completion). Support for a number of the Transaction properties is optional; if an implementation does not support a particular optional feature, then JDOUnsupportedOptionException is thrown if the property is set to true , as shown in Table 5-8. Table 5-8. Transaction Properties
5.3.5.3 Method summaryThe Transaction methods allow an application to explicitly control transactions. If used within a managed environment where transactions are controlled by an external coordinator, any attempt to explicitly begin or end a transaction results in JDOUserException being thrown. Only isActive() can be called when using an external coordinator. void begin() 5.3.5.4 Usage guidelinesUsing the Transaction interface is simple. The following code snippet taken from TransactionExample.java shows how to begin and commit a transaction: PersistenceManager pm = pmf.getPersistenceManager(); Transaction tx = pm.currentTransaction(); tx.begin(); // Do something interesting tx.commit(); The following code snippet taken from NonTransactionalReadExample.java shows how a persistent object can be accessed outside of a transaction using a non-transactional read. If setNontransactionalRead(true) was not supported, then accessing a persistent object outside of a transaction would result in JDOUserException being thrown: Transaction tx = pm.currentTransaction(); tx.setNontransactionalRead(true); tx.begin(); Author author = new Author("Keiron McCammon"); pm.makePersistent(author); tx.commit(); // Author instance transitions to hollow after commit System.out.println( "Author's name is '" + author.getName() + "'."); // Author instance is retrieved from the datastore and // transitions to being non-transactional if (!JDOHelper.isTransactional(author)) { System.out.println("Author is non-transactional."); } The output would be as follows: Author's name is 'Keiron McCammon'. Author is non-transactional. The following code snippet taken from RetainValuesExample.java differs from the previous example only in that it calls setRetainValues() instead of setNontransactionalRead() . In the previous example, the Author instance transitions to being hollow after the commit, and when accessed outside of the transaction, its fields are re-retrieved from the datastore and the instance transitions to being non-transactional. With retainValues() , the Author instance transitions to being non-transactional after the commit and its field values are kept in memory. This has the advantage that the fields of the instance don't have to be re-retrieved again: Transaction tx = pm.currentTransaction(); tx.setRetainValues(true); tx.begin(); Author author = new Author("Keiron McCammon"); pm.makePersistent(author); tx.commit(); // Author instance transitions to being non-transactional // after commit and its fields remain in-memory if (!JDOHelper.isTransactional(author)) { System.out.println("Author is non-transactional."); } System.out.println( "Author's name is '" + author.getName() + "'."); The output would be as follows: Author is non-transactional. Author's name is 'Keiron McCammon'. RetainValues can be used to retain the fields of persistent objects in memory after a transaction commits. However, if a rollback is used instead of a commit, then the in-memory instances retain the values of any fields that were modified during the transaction, even though the modifications have been rolled back in the datastore. If this is undesirable, then the restore values option can be used to revert the modified fields to the values they had at the beginning of the transaction. The following code snippet taken from RestoreValuesExample.java shows that if the name of an Author instance is modified but the transaction is rolled back, then the modified name remains in memory unless RestoreValues is used: Transaction tx = pm.currentTransaction(); tx.setRetainValues(true); tx.begin(); Author author = new Author("Keiron McCammon"); pm.makePersistent(author); tx.commit(); // Author instance is retained after commit and transitions // to being non-transactional if (!JDOHelper.isTransactional(author)) { System.out.println("Author is non-transactional."); } System.out.println( "Author's name is '" + author.getName() + "'."); tx.begin(); author.setName("Sameer Tyagi"); tx.rollback(); // Transaction was rolled back rather than committed // therefore the name change is not in the datastore. // However, the changed name is retained in-memory. System.out.println( "Author's name is '" + author.getName() + "' after rollback."); tx.setRestoreValues(true); tx.begin(); author.setName("Sameer Tyagi"); tx.rollback(); // This time because RestoreValues is true the author's // name is reverted back to its original name on rollback System.out.println( "Author's name is '" + author.getName() + "' after rollback with RestoreValues."); The output would be as follows: Author is non-transactional. Author's name is 'Keiron McCammon'. Author's name is 'Sameer Tyagi' after rollback. Author's name is 'Keiron McCammon' after rollback with RestoreValues. The following code snippet taken from OptimisticExample.java changes the zip code of an Author's address in an optimistic transaction and makes the Author instance transactional. In another transaction, the name of the Author is modified and committed before the original optimistic transaction is committed. When the optimistic transaction is committed, an exception is thrown because the Author instance was modified by the other transaction: Transaction tx = pm.currentTransaction(); tx.begin(); Author author = new Author( "Keiron McCammon", new Address( "6539 Dumbarton Circle", "Fremont", "CA", "94555")); pm.makePersistent(author); tx.commit(); tx.setOptimistic(true); tx.begin(); Address address = author.getAddress(); address.setZipcode("12345"); pm.makeTransactional(author); /* * Another PersistenceManager instance is used to modify * the author's name in a different transaction. */ PersistenceManager pm2 = pmf.getPersistenceManager(); Transaction tx2 = pm2.currentTransaction(); tx2.begin(); /* * The object identity of the Author is used to retrieve * the Author instance within the context of the new * PersistenceManager instance. */ Author author2 = (Author) pm.getObjectById(JDOHelper.getObjectId(author), false); author.setName("Sameer Tyagi"); tx2.commit(); pm2.close(); // Because the Author's name was updated in a different // transaction, the optimistic transaction will throw // an exception on commit. try { tx.commit(); } catch (JDOOptimisticVerificationException e) { System.out.println("Transaction failed."); } If the Author instance hadn't been explicitly made transactional, the commit would have been successful, because in the first transaction the Address instance was modified, and in the second transaction the Author instance was modified, which would not have resulted in a conflicting update. 5.3.6 InstanceCallbacksThe InstanceCallbacks interface provides a means whereby instances of persistence-capable classes can take action on certain state changes in a persistent object's lifecycle. The JDO implementation invokes these methods as persistent objects that are stored, retrieved, and deleted. 5.3.6.1 Method summaryFigure 5-4 provides a simplified view of when the various InstanceCallbacks methods are invoked. Figure 5-4. Invocation of InstanceCallbacks .
When a persistent object is retrieved from the datastore, jdoPostLoad() is invoked. Before a persistent object is sent to the datastore (typically during commit), jdoPreStore() is invoked. If a persistent object is deleted, then jdoPreDelete() is invoked, and before a persistent object transitions to being hollow, jdoPreClear() is invoked. void jdoPostLoad() 5.3.6.2 Usage guidelinesThe following code snippet taken from Author.java shows how jdoPreDelete() can be used to delete an Author's Address and remove the Author from each of its referenced books: public class Author implements InstanceCallbacks { private String name; private Address address; private Set books; // Additional methods not shown public void jdoPostLoad() { } public void jdoPreClear() { } public void jdoPreDelete() { JDOHelper.getPersistenceManager(this). deletePersistent(address); if (books != null) { Iterator iter = books.iterator(); while (iter.hasNext()) { Book book = (Book) iter.next(); book.removeAuthor(this); } } } public void jdoPreStore() { } } |