JDO supports a variety of architectures within the application's JVM context. Your application can have one or multiple PersistenceManagers accessing the same or different datastores concurrently. Each PersistenceManager has its own persistent instance cache and its own associated Transaction instance, which manages a distinct transactional context. A JDO implementation may also maintain a shared cache of instances (not visible to applications) to optimize the application's access of data in the datastore.
The simplest JDO application architecture has a single PersistenceManager, as illustrated in Figure 3-1. A PersistenceManager is the primary interface used by the application to access persistent services. It is an interface that is implemented by an instance of the JDO implementation. The persistent instances are managed in a cache, where they are used directly by the application. The JDO implementation manages the persistent instances both by using application control (e.g., using PersistenceManager and Query methods), and transparently (when the application accesses a field that is not loaded). The cache contains other artifacts, used to track the identity and state of the instances, but these artifacts are not visible to the application. Whenever we mention the cache, we are referring to the cache of persistent instances.
The application cache is not a specific region of memory, as Figure 3-1 might imply; it is simply part of the JVM's object heap. Each persistent class has a field, named jdoStateManager, added by the enhancer to reference a StateManager. The StateManager manages the field values and lifecycle state of the instance, and has a reference to its associated PersistenceManager. A PersistenceManager may use one or more StateManagers; this detail is implementation-specific. The jdoStateManager field for any instance being managed (either a persistent or transient transactional instance) is set to reference a StateManager; otherwise, the jdoStateManager field is null.
A persistent instance in the cache can directly reference other persistent instances in the same cache. You can navigate from one instance to another using standard Java syntax. Instances of transient classes (for example, your application class) can also reference these persistent instances. A persistent instance in the cache can also reference transient instances of both persistent and transient classes. The persistent classes themselves are responsible for managing references to transient instances; the JDO implementation does not manage these references.
Figure 3-2 shows the relationships between the persistent instances, the StateManager, and the PersistenceManager. Each persistent instance contains a reference to a StateManager, which can manage one or more persistent instances. Each StateManager contains a reference to its PersistenceManager, which can manage one or more StateManagers. Each PersistenceManager contains a reference to its PersistenceManagerFactory, which can manage one or more PersistenceManagers. Each PersistenceManager can manage one transaction serially, and contains a reference to its Transaction instance. The PersistenceManager uses a StoreManager to interact with the datastore; this relationship is not defined by the JDO specification.
You can instantiate multiple PersistenceManagers in your application from the same or different PersistenceManagerFactorys. Figure 3-3 illustrates an application with two PersistenceManagers from the same PersistenceManagerFactory.
Each PersistenceManager manages its own transaction context and application cache. In this particular example, both PersistenceManagers access the same datastore and are from the same JDO implementation. This is the typical architecture for managed environments where different instances of the same component access the same datastore via different PersistenceManagers.
Both PersistenceManagers may have the same datastore instance in their caches, represented by different persistent instances. This architecture provides for transactional isolation of changes made to the same datastore instance by different transactions.
Figure 3-4 illustrates PersistenceManagers accessing different datastores. These PersistenceManagers could be from the same or different implementations. For example, one datastore may be a relational database and the other an object database. Due to JDO's binary-compatibility contract (covered in Chapter 6), PersistenceManagers from different implementations can manage different instances of the same persistent classes. JDO is the first database-interface technology to offer this high level of portability across database architectures.
In addition to the application cache, some JDO implementations also maintain their own persistent instance cache that sits between the application cache and the datastore. Your application does not have access to this implementation cache. Its role is to cache the state of objects from the datastore in memory, so they can be provided to the application without requiring access to the datastore. Use of caches can result in significant performance improvements. A shared implementation cache is most useful when you use nontransactional access, covered in Chapter 14, or optimistic transactions, covered in Chapter 15. When you use datastore transactions, the shared cache is usually bypassed.
Figure 3-5 illustrates a shared implementation cache that is managed within a single JVM. It allows each of the PersistenceManagers to quickly access the state of objects that have been accessed from the same datastore.
For example, if one PersistenceManager accesses a particular instance, the implementation needs to read the instance from the datastore. But if the other PersistenceManager then accesses the same instance, the implementation can use the data in the shared implementation cache and avoid having to access the datastore.
Several JDO implementations provide a distributed cache architecture, which allows them to migrate the state of objects between JVMs. Figure 3-6 illustrates this architecture.
Again, the goal with these implementations is to avoid a datastore access whenever possible. For some systems where multiple applications may access the same objects, these implementations demonstrate substantial performance improvements.