JDO Architectures

Editor's Note: In this excerpt from Java Data Objects, authors David Jordan and Craig Russell provide a high-level overview of the architectural aspects of JDO, as well as examples of environments in which JDO can be used.

One of JDO's primary objectives is to provide you with a
transparent, Java-centric view of persistent information stored in a wide
variety of datastores. You can use the Java programming model to represent the
data in your application domain and transparently retrieve and store this data
from various systems, without needing to learn a new data-access language for
each type of datastore. The JDO implementation provides the necessary mapping
from your Java objects to the special datatypes and relationships of the
underlying datastore. Chapter 4 discusses Java modeling capabilities you can
use in your applications. This chapter provides a high-level overview of the
architectural aspects of JDO, as well as examples of environments in which JDO
can be used. We cannot enumerate all such environments in this book, because
JDO is capable of running in a wide variety of architectures.

A JDO implementation is a collection of classes that implement
the interfaces defined in the JDO specification. The implementation may be
provided by an Enterprise Information System (EIS) vendor or a third-party
vendor; in this context, we refer to both as JDO
vendors. A JDO implementation provided by an EIS vendor will most likely
be optimized for the specific EIS.

The JDO architecture simplifies the development of scalable,
secure, and transactional JDO implementations that support the JDO interface.
You can access a wide variety of storage solutions that have radically
different architectures and data models, but you can use a single, consistent,
Java-centric view of the information from all the datastores.

The JDO architecture can be used to access and manage data
contained in local storage systems and heterogeneous EISs, such as enterprise
resource planning (ERP) systems, mainframe transaction processing systems, and
database systems. JDO was designed to be suitable for a wide range of uses,
from embedded small-footprint systems to large-scale enterprise application
servers. A JDO implementation may provide an object-relational mapping tool
that supports a broad array of relational databases. JDO vendors can build
implementations directly on the filesystem or as a layer on top of a protocol
stack with multiple components.

JDO has been designed to work in three primary environments:

Nonmanaged, single transaction
Involves a single transaction and a single JDO implementation, where compactness is the primary concern. Nonmanaged refers to the lack of distribution and security within the JVM. The security of the datastore is implemented by name/password controls.

Nonmanaged, multiple transactions
Identical to the first, except that the application uses extended features, such as concurrent transactions.

Managed
Uses the full range of capabilities of an application server, including distributed components and coordinated transactions.
Security policies are applied to components based on user roles and security domains.

You can focus on developing your application's business and
presentation logic without having to get involved in the issues related to
connecting to a specific EIS. The JDO implementation hides the EIS-specific
issues, such as datatype mapping, relationship mapping, and the retrieval and
storage of data. Your application sees only a Java view of the data, organized
as classes using native Java constructs. EIS-specific issues are important
only during deployment of your application.

In a nonmanaged environment, you do not rely on the managed
services of security, transaction, and connection management offered by a
middle-tier application server. Chapters through cover the uses of JDO in a
nonmanaged environment, most of which also apply to a managed environment.

When JDO is deployed in a managed environment, it uses the J2EE
Java Connector Architecture, which defines a set of portable, scalable,
secure, and transactional mechanisms for integrating an EIS with an
application server. These mechanisms focus on important aspects of integration
with heterogeneous systems: instance management, connection management, and
transaction management. The Java Connector Architecture enables a standard JDO
implementation to be pluggable across application servers from multiple
vendors.

Managed environments also provide transparency for application
components' use of system-level mechanisms--distributed transactions,
security, and connection management--by hiding the contracts between JDO
implementation and the application server. Chapter 16 covers the use of JDO in
the web server environment. Chapter 17 explains how to use JDO to provide
persistence services in a J2EE application-server environment, which supports
the Enterprise JavaBeans (EJB) architecture.

Multiple JDO implementations--possibly multiple implementations
per type of EIS or local storage--can be plugged into an application server
concurrently, or they can be used directly in a two-tier or embedded
architecture. JDO also allows a persistent class to be used concurrently with
multiple JDO implementations in the same Java Virtual Machine (JVM) or
application-server environment. This enables application components--deployed
on a middle-tier application server or client-tier--to access the underlying
datastores using the same consistent, Java-centric view of data.

The persistent classes that you define can migrate easily from
one environment to another. This also allows you to debug persistent classes
and parts of your application code in a simple one- or two-tier environment
and deploy them in another tier of the system architecture.

Architecture Within Application JVM

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.

Single PersistenceManager

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.

Figure 3-1. Application using a single PersistenceManager to access a datastore

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.

Figure 3-2. UML diagram of persistent instance cache

Multiple PersistenceManagers Accessing the Same Datastore

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.

Figure 3-3. Application with multiple PersistenceManagers

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.

Multiple PersistenceManagers Accessing Different Datastores

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.

Figure 3-4. Application with multiple JDO implementations

Shared Implementation Cache

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.

Shared implementation cache within a single JVM

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.

Figure 3-5. Implementation of a shared cache for transactions
accessing 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.

Shared implementation cache distributed among JVMs

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.

Figure 3-6. Implementation use of distributed, synchronized caches

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.

Datastore Access

We have explored the architecture in the application's JVM and
discussed the application cache and implementation cache. Now let's examine
the architectures of JDO implementations. We'll discuss each type of datastore
separately.

These architectures don't affect your application's programming
model, but they affect the configuration of the environment in which your
application executes. In particular, the ConnectionURL property of the Properties instance used to construct the PersistenceManagerFactory refers to a local or remote
datastore.

Direct Access of Filesystem or Local Datastore

Some JDO implementations store the objects directly in a local
filesystem or datastore. Figure 3-1 illustrates this architecture. There is only a single process context in this architecture. The JDO implementation uses the Java I/O classes
directly to manage the storage of the objects in a file. The JDO Reference
Implementation implements this architecture, as do some object databases.

Remote Access of a JDO Server

Some JDO implementations connect to a separate server that
manages the datastore, as illustrated in Figure 3-7. The JDO Reference Implementation implements this architecture, as do most object databases. In this particular example, the JDO implementation
itself provides a server built specifically for object storage, which then
manages the filesystem directly. The component that executes in the same JVM
as the JDO implementation and communicates with the remote server is called a
resource adapter. The protocols between the client JVM
and the JDO Server are vendor-specific.

Figure 3-7. Client access of a JDO server

Remote Access of a SQL Datastore

Figure 3-8 illustrates the use of a relational database server for object
storage. This is the most common architecture used by current commercial JDO
implementations. Since the application is written in Java, the JDO
implementation uses JDBC to communicate with the database server. When you
deploy your application, you use a proprietary tool supplied by the JDO vendor
to map your application's Java objects to tables in the relational database.
Some JDO implementations use your application's persistent object model to
create the relational schema for you.

Figure 3-8. Client access of a SQL datastore

The relational vendor or a third party provides a JDBC driver to
communicate with the database, using protocols specific to the database. The
JDBC driver is the resource adapter in this architecture.

Since the JDBC interface is well defined, this architecture
offers a high degree of portability. JDO implementations have been written to
use a variety of datastores that provide a JDBC driver implementation. While
the JDBC interface is standard, the SQL data manipulation language, used by
the relational databases, varies considerably; the JDO implementation hides
these differences from JDO applications.

System Architectures with a JDO Application

Now we'll examine where JDO objects and application logic can be
placed relative to an application's overall system architecture, including
both managed and nonmanaged environments. In the remaining examples in this
chapter, we don't show the details of how the JDO implementation manages the
storage for the persistent instances.

JDO Rich Client with Local Datastore

The simplest form of system architecture is a one- or two-tier
application that may be executed from the command line, from a shell script,
or via a graphical user interface. We refer to the application as a rich client to distinguish it from a browser that simply displays HTML and executes applets. The application uses local filesystem and
JDO persistent services directly.

JDO Applications in a Web Server

Figure 3-9 illustrates how an application can use JDO to provide persistent
services to the implementation of a web servlet or JavaServer Pages (JSP).
When using JSP pages, the application typically will use JDO in one of two
ways: by calling JDO's APIs directly in Java, or using a JSP tag library to
abstract the JDO API (similar to the way the JSP Standard Tag Library
abstracts the JDBC API).

Figure 3-9. JDO application running in a web server

With this architecture, the servlet/JSP page gets data from the
browser in the form of strings from an HTTP doGet( ) or doPost( ) request and uses JDO to
implement the request. Your application may use the Struts framework to implement the servlets and JSP pages
in this architecture. We will discuss the web-server access patterns in detail
in Chapter 16.

JDO Applications as Web Services

Figure 3-9 also illustrates the use of JDO as the persistence implementation for
a web server implementation of a web services endpoint. The web server may
register the service using UDDI and a registry service, and clients may find
the service via the same registry.

A web server implementation uses a servlet to implement the
service endpoint. The servlet can use the JDO API for the persistent service,
exactly as it does for servicing HTTP requests. The primary difference between
SOAP and standard HTTP is that with SOAP requests, the message data in the
HTTP message is formatted as SOAP XML instead of get/post data.

Rich Client Connecting to Application Server with EJB Components

Figure 3-10 illustrates a rich client connecting directly to an application
server using EJB beans. This architecture typically is implemented behind the
firewall of a company, as it directly exposes enterprise services to clients.
The clients use the JNDI services of the J2EE client container to look up
services by name (including EJB beans) and to connect to the server via
RMI/IIOP or a proprietary protocol. Alternatively, a client may use SOAP
protocols to access the middle-tier server.

The EJB components inside the EJB container use other EJB
components to implement their services. They use a combination of JDBC and JDO
to access persistent services. Session beans and message-driven beans use JDO
and JDBC directly. Entity beans use JDO transparently (the container
implements CMP entity beans using JDO but does not expose JDO as an API to the
CMP developer).

Web Server with EJB Server

Figure 3-11 illustrates servlets and JSP pages that use the services of an EJB
container to implement the business logic of an enterprise application. The
EJB beans executing inside the EJB container use JDO as their persistence
service. The web and EJB containers often reside in the same JVM in this
architecture, even though they represent different tiers of the architecture.

EJB Session Beans Using Session Bean Fašades

Figure 3-12 illustrates the session bean delegating parts of the business logic
to session bean fašades that use JDO as their implementation. This
architecture allows location transparency among the components. For example,
if the session bean that interacts directly with clients delegates part of the
functionality to other session-bean components, this architecture allows the
other components to be located in different machines. Chapter 17 describes
this architecture in detail.

Figure 3-12. EJB session beans using session bean delegates

JDO Providing Container-Managed Persistence

As a side note, an EJB server may implement J2EE
container-managed persistence (CMP) entity beans using JDO as the persistence
layer. The J2EE components and the users of these components are unaware that
JDO is used for the implementation of the persistence
service.

David Jordan
founded Object Identity, Inc. to provide Java Data Objects (JDO) consulting and training services. David is also a coauthor of O'Reilly's book on Java Data Objects, with Craig Russell.