|
Comments
Did you read today's front page stories & breaking news?
SYS-CON.TV
|
General Java Applying Patterns to JDBC Development
Applying Patterns to JDBC Development
Jun. 1, 2000 12:00 AM
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 JDBCODBC 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)
public static class ConnectionPooler implements . . . 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 . . .
Object Pool Pattern (Grand Creational Pattern)
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)
public synchronized RemoteResultSetIF executeQuery(String query) . . . Observer Pattern in Detail (GoF Pattern)
appServer = (RemoteAppServerIF) Naming.lookup(base_url + "/AppServer"); 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 Other Database-Friendly Patterns Worth Mentioning
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. 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). 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
Reader Feedback: Page 1 of 1
Latest Cloud Developer Stories
Subscribe to the World's Most Powerful Newsletters
Subscribe to Our Rss Feeds & Get Your SYS-CON News Live!
|
SYS-CON Featured Whitepapers
Most Read This Week
Breaking Cloud Computing News
|
|||||||||||||||||||||||||||||||||||||||||||||||||