HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

Developers at some point in their careers will find themselves standing at the whiteboard, trying their best to regurgitate some complex development design they've spent all night working on. This is usually done with a series of strange symbols, arrows and scribblings in an attempt to convey the clarity that may lie in the head of said developer (unless of course he or she doesn't know what exactly the design is supposed to look like). Either way, you have the same problem.

In an object-oriented fashion, how do you make tangible a design that's intangible yet very repeatable? How do you communicate ­ or in the latter case, how do you come up with ­ something you know in your heart you've done before but maybe in a slightly different way. And I'm not talking about UML. UML helps, but it provides only the hieroglyphics you may need to communicate your design. What you need is a repeatable way to show your design efforts without reinventing the proverbial design wheel by going through every step you originally used to solve the problem. If you're a developer or architect, for example, and you haven't gone through this, you probably will. Welcome to the world of patterns!

Rarely does a developer come up with the right design on the first attempt. Typically, each design must go through a bit of morphing before you can call it a sound one. And what about reusability? Isn't that one of the primary goals of object-oriented design? For both new and experienced designers the challenge of object-oriented design is difficult enough. In addition to being an outstanding communications tool, patterns help make the process of coming up with an elegant object-oriented design easier. More important, they help make a design reusable. Reusability applies not only to the objects themselves but also to the process used to come up with them. That's where patterns fit in ­ they help make object-oriented designs adaptable, sophisticated and, most important, repeatable.

When referring to patterns in this article I leave out the word design as I'm covering implementation patterns as well. Unlike what you find in some pattern books (although most I've read are excellent), I wanted to create a practical piece of code that uses both patterned design and patterned implementation ­ not another "gutted" bank application but an actual piece of code that could be used in a real-world business application. Something I might use myself with some adjustments. Now that's not to say the code I included for download is production ready (see the JDJ Web site, www.JavaDevelopersJournal.com); in fact, it isn't! However, I hope the included code will provide you with a base upon which to build something useful.

Initially I'll cover some of the most widely used patterns to implement a JDBC database connection "pooler," something usually found in a distributed EJB development tool such as WebLogic or in other n-tier environments, such as Microsoft's Transaction Server. Even the newest flavor of ODBC has a connection-pooling mechanism built in. Nonetheless, using such a sophisticated environment for something like object pooling may be overkill; for pure Java environments, relying on ODBC connection pooling by using JDBC­ODBC as a solution isn't much better. Either way, I hope my connection pooler will provide some good examples of a few of the primary patterns used for database development and help you get your feet wet in the world of using patterns.

If you're new to patterns, one of the better places to start with is the "Gang of Four" (GoF). No, I'm not talking Spanky, Alfalfa and the gang, but Gamma, Helm, Johnson and Vlissides. Besides Christopher Alexander, who first spoke of design patterns when referring to buildings architecture, the Gang of Four, authors of Design Patterns: Elements of Reusable Object-Oriented Software (Addison Wesley), present some of the original thoughts on object-oriented design patterns from a language-agnostic viewpoint. Although they often mention C++ and Smalltalk, the book is geared to the patterns themselves, not the language used to write them. This is what makes patterns useful ­ their general applicability to all languages. For the Java developer this is all fine and good, but if you're like me, a little sample code always helps me to swallow dry subjects. That's why I highly recommend reading Patterns in Java, Volumes I and II by Mark Grand. These books cover patterns specifically articulated by the author and some of the more popular patterns covered in the Gang of Four books. They also cover other pattern originators such as Craig Larman whose "General Responsibility Assignment Software Patterns" (GRASP) present the more fundamental object-oriented ideas in the form of design patterns. Most important, Grand uses Java to exemplify each pattern. The source code is actually useful too.

What makes up a pattern? Or a design pattern in particular? Well, the Gang of Four breaks a pattern's description into textual sections that help explain the pattern in detail and show its context. They go on to explain that each pattern should highlight intent, motivation, applicability, structure (e.g., UML notation) and consequences, all of which help to describe the pattern as a whole. In addition, areas such as the pattern's design participants, its collaborations with other elements and its implementation help provide elemental detail so the pattern can be applied to a specific design. A pattern's sample code, known uses, alternative names and related patterns also contribute to its understanding.

Most of these areas are self-explanatory; for some, patterns may even overlap in meaning. Thus the gang also recommends that patterns be placed into certain pattern "classifications" based on principles such as the pattern's purpose and scope. These categories include creational, structural and behavioral. Each classification helps the developer look at a pattern in a particular perspective, which allows each pattern to fulfill different forms. The end result is that implementations are based not only on the pattern but also its classification. For example, some of the patterns I describe and apply in this article qualify as traditional GoF "creational" patterns and, when coupled with other complementary patterns, can be classified as distributed database patterns as well. I use the Singleton (GoF), Delegation (Grand), Observer (GoF) and Object Pooling (Grand) in this way in the connection pooler code outlined below. I also refer to other related patterns such as Bounded Buffer (implementation), Exception Chaining (implementation) and Guarded Suspension (design) throughout.

I've limited the description of the patterns I use in the code example to a few "consolidated descriptives" that should provide enough information for you to grasp their meaning without my going into pages of detail. I'll show you how to apply these patterns to a database connection pooler component that can run either locally in a more traditional fat-client model or, in an attempt to create a more thin-client model, remotely, by using RMI to run the connection pooler on the server. The choice is yours. Although this example is written and tested in both environments, keep in mind that it has been designed primarily to run in a threaded RMI server. If run locally, you should make minor modifications to improve performance (e.g., remove synchronization qualifiers).

Okay, let's talk about a few common patterns that can be applied to a typical database implementation and, in my case, the connection pooler.

Singleton Pattern (GoF Creational Pattern)

  • Intent: To ensure that the class has only one instance and to provide a global point of access to it.
  • Motivation: Sometimes only one instance of an object can exist. For example, although there are many database connections, there's only one database connection pooler to control them.
  • Context: An instance operation must be defined that allows clients access to the singleton object through this operation only. Using this single-access point of control improves namespace issues by eliminating the need for global references. Limited resources are typically controlled using this pattern so items such as connections can be controlled, pooled and dispensed with.
  • Solution and structure: The structure is simple as it's implemented using a single class with one static instance operation that's used to return a unique instance variable. Typically, such classes have private constructors that don't allow external clients to dynamically instantiate this class, thus forcing them to use the instance operation only (see Figure 1).

    Figure 1
    Figure 1:

  • Implementation: Singleton implementations can vary as long as only one instance of the class is allowed and the creation sequence is accessible by all clients. In the database connection pooler, the pooler can run locally or remotely. Whether you run this code remotely using RMI or CORBA or just locally, the code should be equally effective at pooling connections. If run locally (without RMI), the connection pooler object follows the singleton pattern in a purist fashion since each database client can have only one instance of the pooler. This is controlled by using a static instance operation that returns the instance variable for the pooler that's allocated during first activation.

public static class ConnectionPooler implements . . .
{ private static ConnectionPooler thePooler = new ConnectionPooler();
private ConnectionPooler() { . . .}

public synchronized static ConnectionPooler getDbConnectionPooler()
{
return thePooler;
}
...

When run remotely, which we'll cover later, the RMI server controls the instantiation of the connection pooler in a singleton fashion without requiring you to control single instantiation at the connection pooler level. In other words, although the connection pooler is treated as a singleton, it's done so through the RMI server, not the connection pooler itself; otherwise, each client would receive its own connection pooler when run remotely, which I'm trying to avoid.

The following shows how the singleton pattern can be implemented in various ways as long as the foregoing principles hold true.

public class AppServer extends UnicastRemoteObject implements . . .
{
public static RemoteConnectionPoolerIF rcp = null;
public synchronized SessionIF createSession(String dbURL) . . .
{
if (rcp == null) // only allocate one pooler per appserver
{
. . .
rcp = (RemoteConnectionPoolerIF)
DbConnection.ConnectionPooler.getDbConnectionPooler();
}
else
. . .

Object Pool Pattern (Grand Creational Pattern)

  • Intent: To control the use of a limited resource, typically expensive to create, and/or a resource limited by the number of objects allowed to be created.
  • Motivation: To increase application performance and prevent valuable and expensive resources from being exhausted, you must control them through pooling.
  • Context: Aside from network connection initialization, creating connections to a database can be the single most expensive operation in an application. Pooling these connections provides dramatic application performance increases through repetitive database access. Controlling them also provides a means for keeping allocated resources under control (e.g., memory) in distributed architectures for the client as well as the server.
  • Solution and structure: The structure of the pooling pattern contains the reusable objects, the client that uses them and a reusable object pool (see Figure 2). New instances are to be controlled and are usually limited so clients can reuse objects instead of creating new ones. For database connections it's usually necessary to assume that each reusable object is identical so that when run in a remote environment, with many different clients, each connection can be treated the same (this will lead to lengthy and usually heated security discussions with the database folks). They're all identical in nature. When run locally, it's possible to uniquely identify each resource object that's managed, but the client is then required to pass in specific qualifiers (user ID and password), cluttering up the code.

    Figure 2
    Figure 2:

  • Implementation: In the connection pooler example, an RMI client (AppClientTest.java) represents a client. During session creation a client is given access to a session that allows it to acquire and release connections at will. The connection object is represented by the DbConnection class, which acts as a wrapper between the client and the actual JDBC code (see "Delegation Pattern" section below). The pooler, represented by a nested class within DbConnection, manages the connections by first initializing and caching them (caching is optional). Operations within the connection pooler allow each session to acquire and later release each connection. Whether new connections are continuously created on request or become "guarded" and wait for connections to be returned by other clients is an option controlled by the developer.

Once received at the client, the client uses the connection like any other JDBC connection by issuing queries, updates or any other supported database operation. Each database operation is handled in a delegated fashion by JDBC, limited only by the JDBC driver used by the connection object. When a data operation is complete, the client releases the connection back to the connection pooler (releaseDbConnection()), which returns it to the pool at that time. This immediate acquire-and-release allows the client code to concentrate on the operation at hand, not the specifics of holding onto connections for performance reasons.

Precaching connections is optional and in our source code occurs during initialization, which is kicked off when the first client creates a session at startup. At that point a predetermined number of connections is acquired and placed into the pool for immediate availability. This allows access times to increase dramatically for additional online clients that require connections, thus boosting performance of the application as a whole. Guarded suspension is used when using precached connections only (see sidebar).

Delegation Pattern (Grand Fundamental Pattern)

  • Intent: Delegation allows the developer to extend and reuse functionality of the existing class without using inheritance.
  • Motivation: Avoiding inheritance may be optional or required, depending on the implementation. If a class can't be inherited or doesn't fit the "is a kind of" relationship to its parent, delegation should be used.
  • Context: To pass a JDBC result set across the wire using RMI, it must either be "remoteable" (in the sample code RemoteResultSet extends RMI's UnicastRemoteObject and implements RemoteResultSetIF) and/or "serializable." This is required for RMI to allow a result set to be marshaled across the network. A JDBC result set, by default, doesn't fulfill either of these requirements, so delegation is used to assign remoteable calls across the network to its JDBC equivalent on the server. (See DbConnection.java ­ executeQuery() on the JDJ Web site.) For iterative next() queries (see AppClientTest.java on the JDJ Web site), this is not the most efficient way to query data since each record has to perform a network round-trip to complete. For a production environment it's recommended that a form of prefetching, similar to JDBC's own prefetching, is used to prequery results before passing them across the network. In fact, in most distributed database applications business services acting as connection clients typically fill that role from the server side, not the client side.

  • Solution and structure: Using delegation, an object can assign operations to different objects at different times. Its structure (see Figure 3) is simply shown with the objects it delegates to as arrows leading to the delegator instead of to the traditional inheritance class diagram.

    Figure 3
    Figure 3:

  • Implementation: For the connection pooler component to run on the server, a number of objects must be made remoteable (primitive remote without modification). These include the reusable connection object, connection pooler and result set that a query returns. Run locally, the connection pooler can use the default JDBC result set, but when run remotely I had to create my own version. To avoid having to reimplement a full result set from scratch, delegation can be used to assign remote requests from the delegator (RemoteResultSet) to the JDBC result set. When executeQuery() is called on the connection object, a new RemoteResultSet is created, passing into its constructor the result set that returned from JDBC's executeQuery(). Once the RemoteResultSet has the original JDBC result set, it simply delegates all calls to it (see previous paragraph on iterative next() calls).

public synchronized RemoteResultSetIF executeQuery(String query) . . .
{
stmt = connection.createStatement();
rs = stmt.executeQuery(query);

rsRemote = new RemoteResultSet(rs);

return (RemoteResultSetIF) rsRemote;
}

Observer Pattern in Detail (GoF Pattern)

  • Intent: To provide a mechanism that allows objects to dynamically subscribe to state change notifications from another object.
  • Motivation: When client objects communicate with a server object of some kind, it would be beneficial for them to be made aware of server-side state changes in that object. This allows subscriber clients to react to the state changes in a more dynamic fashion, thus adding robustness to the application.
  • Context: Suppose a client communicates regularly with a server object but that communication is time-critical. As with the connection pooler component, each initial connection to the connection pooler causes it to start up and fill the pool with connections. This could take several seconds, depending on how many connections the capacity is set for. Before this occurs it may be beneficial for said client to be notified when a server is online or going offline, thus giving it the opportunity to determine the time it will take to make such a connection. Not all clients may want this service, so the subscriber ­ or "observer" in this case ­ should have the option of observing only. The server, or observable object in this case, just notifies all observers of the state change, not caring which objects they're actually observing. Take heed, however: it could be a significant performance hit if hundreds of clients need to be notified. In this case the notification mechanism would be better served by running from a separate multicasting thread.

12 of 16

  • Solution and structure: The Observer pattern is made up of an observer class that implements an interface containing a method the observable will call during notification. On the other side is the observable class, which implements an interface that the observer calls to register with the observable. The observer calls registerObserver() or addObserver() whenever that client wishes to receive notifications. The observable then keeps a list of observers and, during state changes, calls the notification method on the interface passed to it by the observer during registration. To stop receiving notifications, the observer simply unregisters with the observable object in similar fashion (unregisterObserver() or removeObserver().
  • Implementation: The connection pooler implementation of this pattern is simple (see Figure 4). The AppClientTest implements RemoteObserverIF, which contains the method that the observable will call during a state change. In the RMI test client (AppClientTest), after the RMI server is started the client registers with the server by calling addObserver().

Figure 4
Figure 4:

appServer = (RemoteAppServerIF) Naming.lookup(base_url + "/AppServer");
if (appServer == null)
{
return;
}

appServer.addObserver(this);

At this point the RMI server keeps track of this client. When a client calls closeSession() to close its own session, the server checks to see if there are any remaining connected clients. If there are still active clients, a message notification is sent to all observers that at least one session is active and the server won't shut down (until the last session closes). For this example I display a message only, but with a bit more work this could actually do something useful.

if (sessionCount == 0) // no more clients so close down the pool
closeConnectionPooler(dbURL);
else
remoteNotify("Active sessions still remain pooler still running...");

Other Database-Friendly Patterns Worth Mentioning

  • Guarded suspension (Lea): This is implemented in the connection pooler code (see sidebar).
  • Balking (Lea): Instead of using guarded suspension, the balking pattern could throw an exception instead of waiting for the correct state to occur.
  • Producer­consumer (Grand): Use for asynchronous transactions in which a producer can push data objects onto a queue and a consumer can pop those objects for later processing (e.g., database error logger).
  • State (GoF): Enhances the Session class in the connection pooler component to use a separate concrete state object, subclassed from an abstract state object. State-based behavior can be better decoupled using just one class to enhance reusability.
Developing a Database Connection Pooler
Now that I've covered the patterns from a structural perspective, I'll apply them to a fully implemented database connection pooler set of components (see Figure 5). This example is written as an RMI-based client/server Java application with the AppClientTest (RMI client) and the AppServer (RMI server) as the two major drivers of both client and server. This was written with Symantec's VisualCafé Enterprise Version 3.1 using JDK 1.2.2. I tested two Type 4 JDBC drivers: Oracle's Type 4 Thin Driver Version 8.16 against an Oracle 7 Database and the MS SQL Server 4 JDBC/Kona Type 4 Driver that works with the latest version of BEA's WebLogic going against MS SQL Server 7.0. For the example I connected to the "pubs" database and queried the authors' table with a reusable DbConnection object for testing.

Figure 5
Figure 5:

Note: As mentioned earlier, executing queries remotely should be optimized to precache data that hasn't been implemented for this example. Precaching would avoid unnecessary network round-trips during iterative record traversals (e.g., using ResultSet.next()).

First unzip objectpool.zip (keeping directory paths is recommended) and edit both RunAppServer.bat and RunClient.bat to use your current class path (this must include the class path of the JDBC drivers you decide to use). Once your JDBC driver is installed and tested, open the objectpool.vep project (if you're using Café) and perform a full build; otherwise, compile individually.

If you're running from Café, turn off the automatic RMI compilation step. By default, VisualCafé will try and run "rmic" to build the RMI marshaling code during a full build ­ this may crash your system (this has been reported to Symantec/BEA support). To turn this off, from "project/options/compiler/compiler category/advanced," type "­normi" in the Custom Compiler Flags edit box and rebuild.

Once the project is built or all classes have been compiled, you can run RunRmic.bat from the command line to build the RMI marshaling code all at once. Now you're ready to run.

To run the app server from a command-line window, simply run RunAppServer.bat (edited with your current classpath). This should automatically start the RMI registry and the RMI server, which will begin "listening" for client connections. Once the server is running, from a separate command-line window, run RunClient.bat. This should begin the AppClientTest RMI client. When started, the AppClientTest will connect with the AppServer using RMI, create a session and begin acquiring connections using the session object. After the connections have been acquired, a simple query is made on the database, after which the user can select which action to perform next. When the first session is created, the AppServer will initialize the connection pooler and pass into it the number of connections to cache. This is where the database connections are first established and placed into the pool. This process may take a few seconds (for the first client), depending on how many connections you want to cache (see ConnectionPooler.Initialize() for details). Subsequent client connections (since they're now pooled) will be much faster.

You can fire up additional clients at any time using a separate command-line window. During the demo keep in mind that the connection pooler will block until some connections have been placed or returned into the connection pool, timing out if too many connections are requested at once (this can be adjusted). To the server each command-line window running AppClientTest represents a different client. This will demonstrate a simple concurrent environment. Note also that the connection pooler can be adjusted to run locally without RMI as well as with noncached connections. The choice is yours.

The connection pooler is actually made up of several components broken up into classes and files (see Table 1).

Table 1

As mentioned earlier, the connection pooler is implemented as an inner class of the main DbConnection class using the singleton pattern. The AppServer, when running remotely, actually controls the singleton nature of the connection pooler by creating a new connection pooler only during the first connection. After that, each subsequent client request uses the existing connection held by an instance variable in the AppServer. When run locally, the connection pooler will run as a singleton as expected.

After initialization, the connection pooler class will use guarded suspension when connections have been cached (optional) to control the number of connections allocated. During operation, each client first requests a session object by calling createSesssion(), then a connection object (DbConnection) by calling acquireRemoteDbConnection() using the session object. Once a connection object is retrieved from the pool, the client can then use any of the remoteable operations on that connection (e.g., executeUpdate, executeQuery). When the client completes its operations, it will first release the connection by calling releaseRemoteDbConnection() using the session object, and finally close the session by calling closeSession(). The rest is up to you.

Summary
This article has focused on the real-world database use of design and implementation patterns. Although not quite ready for production, you can use the base code and apply it to most database applications, giving you yet another tool to avoid reinventing the design wheel and freezing at the whiteboard.

Author Bio
Christian Thilmany is president of The eTier Group, Inc., in Houston, Texas. He has over 11 years' experience in distributed application architectures for Internet, intranet and client/server development using Java, C++, Visual Basic and more. Christian is a Microsoft Certified Solutions Developer.
[email protected]

 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.