Etapa 3. Actitudes ante los conflictos
B. EVALUACIÓN DEL PROYECTO 2.7 Tipo de evaluación
3.4. Recomendaciones y sugerencias
The final piece of my connection-caching solution is a class to manage cached connections, doling them out to servlets as they are needed. The CacheConnection class, shown in Example 4-8, does this.
Example 4-8. The CacheConnection class to manage cached connections
import java.io.*; import java.sql.*;
import java.util.Vector;
public class CacheConnection {
private static boolean verbose = false; private static int numberConnections = 0;
private static Vector cachedConnections = new Vector( ); private static Thread monitor = null;
private static long MAX_IDLE = 1000*60*60;
synchronized public static Connection checkOut( ) { return checkOut("Database");
}
synchronized public static Connection checkOut(String baseName) { boolean found = false;
CachedConnection cached = null;
if (verbose) {
System.out.println("There are " + Integer.toString(numberConnections) + " connections in the cache");
System.out.println("Searching for a connection not in use..."); }
for (int i=0;!found && i<numberConnections;i++) { if (verbose) {
System.out.println("Vector entry " + Integer.toString(i)); }
cached = (CachedConnection)cachedConnections.get(i);
if (!cached.isInUse() && cached.getBaseName( ).equals(baseName)) {
if (verbose) {
System.out.println("found ca ched entry " + Integer.toString(i) + " for " + baseName); } found = true; } } if (found) { cached.setInUse(true); } else { if (verbose) {
System.out.println("Cached entry not found ");
System.out.println("Allocating new entry for " + baseName); }
cached = new CachedConnection(
Database.getConnection(baseName), true, baseName); cachedConnections.add(cached);
numberConnections++; }
if (monitor == null) { monitor = new Thread( new Runnable( ) { public void run( ) {
while(numberConnections > 0) { runMonitor( );
}
monitor = null; if (verbose) {
System.out.println("CacheConnection monitor stopped"); } } } ); monitor.setDaemon(true); monitor.start( ); } return cached.getConnection( ); }
synchronized public static void checkIn( Connection c) { boolean found = false;
boolean closed = false; CachedConnection cached = null; Connection conn = null; int i = 0;
if (verbose) {
System.out.println("Searching for connection to set not in use...");
}
for (i=0;!found && i<numberConnections;i++) { if (verbose) {
System.out.println("Vector entry " + Integer.toString(i)); }
cached = (CachedConnection)cachedConnections.get(i); conn = cached.getConnection( );
if (conn == c) { if (verbose) {
System.out.println("found cached entry " + Integer.toString(i)); } found = true; } } if (found) { try { closed = conn.isClosed( ); } catch(SQLException ignore) { closed = true; } if (!closed) cached.setInUse(false);
else { cachedConnections.remove(i); numberConnections--; } } else if (verbose) {
System.out.println("In use Connection not found!!!"); }
}
synchronized private static void checkUse( ) { CachedConnection cached = null;
Connection conn = null; int i = 0;
long now = System.currentTimeMillis( ); long then = 0;
for (i=numberConnections-1;i>-1;i--) { if (verbose) {
System.out.println(
"CacheConnection monitor checking vector entry " + Integer.toString(i) + " for use..."); } cached = (CachedConnection)cachedConnections.get(i); if (!cached.isInUse( )) { then = cached.getLastUsed( ); if ((now - then) > MAX_IDLE) { if (verbose) {
System.out.println("Cach ed entry " + Integer.toString(i) +
" idle too long, being destroyed"); }
conn = cached.getConnection( );
try { conn.close( ); } catch (SQLException e) { System.err.println("Unable to cl ose connection: " + e.getMessage( )); } cachedConnections.remove(i); numberConnections--; } } } }
private static void runMonitor( ) { checkUse( );
if (numberConnections > 0) { if (verbose) {
System.out.println("CacheConnection monitor going to sleep"); }
try {
// 1000 milliseconds/second x 60 seconds/minute x 5 minutes monitor.sleep(1000*60*5);
}
catch (InterruptedException ignore) { if (verbose) {
"CacheConnection monitor's sleep was interrupted"); }
} } }
public void finalize( ) throws Throwable { CachedConnection cached = null;
for(int i=0;i<numberConnections;i++) {
cached = (CachedConnection)cachedConnections.get(i); if (cached.getConnection( ) != null) {
if (verbose) {
System.out.println(
"Closing connection on Vector entry " + Integer.toString(i)); } try { cached.getConnection().close( ); } catch(SQLException ignore) {
System.err.println("Can't close connection!!!"); }
} }
numberConnections = 0; }
public static void setVerbose(boolean v) { verbose = v;
} }
This sample caching object is quite lengthy, but I figure you want a working example, and this is what it takes to get one. Let's start dissecting this class. To begin with, notice that the
CacheConnection class has several static attributes, and that all the methods are static as well. That's because this utility class, like the Database class in Example 4-7, is never intended to
be instantiated in a servlet. The attributes in the class are:
verbose
A boolean used throughout the class's methods to turn diagnostic output on or off. Diagnostic output is written to the standard output device.
numberConnections
An integer to keep track of the number of open connections in the cache.
cachedConnections
A Vector object to contain the actual cache of connections.
monitor
A Thread object that runs independently of the CacheConnection object to manage the removal of unused connections in the cache.
MAX_IDLE
A long to hold the maximum time, in milliseconds, that an idle connection should remain in the cache.
Now that you're familiar with the CacheConnection class's attributes, let's look at the class's methods. In the discussion that follows, I'll work my way down from the top of the class listing and discuss each method in turn.
At the top of the listing, you'll find a pair of overloaded checkOut( ) methods. The first
checkOut( ) method allocates a database connection from a default pool, while the second allocates a connection from a pool specified by name. The default pool name used by the first
checkOut( ) method is "Database". To allocate a connection from that default pool, the first
checkOut( ) method simply calls the second checkOut( ) method, passing "Database" as the pool name parameter. The second checkOut( ) method does all the real work. It looks in the cache for a free connection with the corresponding pool name. If such a connection exists, it is flagged as in use, and returned as the method's result. Otherwise, if no connection exists in the specified pool, a new connection is created, placed into the cache, flagged as in use, and
returned as the method's result. Before returning any connection, the checkOut( ) method checks if a monitorThread object exists, creating one if necessary. I'll cover the function of this
monitorThread object shortly.
The next method, checkIn( ), is used by a servlet to return a connection to the cache when it is no longer needed. Besides returning the connection to the cache, checkIn( ) verifies that the connection is still open. This check is performed to allow a servlet to close a connection should a catastrophic error occur. If the connection is no longer open, the CachedConnection
object that holds the connection is removed from the cache. By closing a bad connection and then returning it to the cache, a servlet can permanently remove that connection from the cache, thereby preventing another servlet from using it.
The CacheConnection class's checkUse( ) method is called by the runMonitor( )
method, which is in turn called by the monitor thread. The purpose of the checkUse( )
method is to close any connections that have been idle longer than the time period specified by the MAX_IDLE attribute. The MAX_IDLE attribute specifies the maximum idle time in milliseconds. In Example 4-8, I've specified a value that results in a maximum idle time of 60 minutes. If you set the MAX_IDLE attribute to a lower value, such as 1000*60*2, or two minutes, you can easily watch the monitor thread close idle connections.
The runMonitor( ) method invokes checkUse( ) to check the cache for idle connections. The runMonitor( ) method then puts the Thread object to sleep for five minutes. After the sleep interval, the runMonitor( ) method awakens, and the cycle repeats. When there are no connections remaining in the cache, the monitor thread terminates.
The setVerbose( ) method allows you to control the display of debugging output. Calling
setVerbose( ) with an argument of true puts the CacheConnection class, as well as all cached CachedConnection objects, into verbose mode. You'll have to activate this from one of your servlets by calling CacheConnection.setVerbose(true). This causes the
CacheConnection object to execute the various System.out.println( ) calls coded within its methods. The resulting debug output is written to your servlet container's error log or to your monitor screen, depending on how your servlet container is configured. Call setVerbose( )
with an argument of false to turn verbose mode off.
The final method is finalize( ) (pun intended). When the servlet container is closed, the
finalize( ) method sweeps through the cache and closes any open connections. 4.2.4.4 A servlet that uses cached connections
Now that you understand the mechanics of the connection cache, let's put it to use. Example 4- 9 shows a servlet that implements a cached connection strategy using the three classes just described. The servlet's name is CachedConnectionServlet.
As you read through the code for CachedConnectionServlet, note that there are three significant differences between it and the SessionLogin servlet you should look for:
1. The servlet turns on our Connection Manager's verbose output mode with a call to the
CacheConnection.setVerbose( ) method. 2. The servlet allocates a cached connection by calling the
CacheConnection.checkOut( ) method. Here, the code is quite compact when compared to the lengthy code required to manage a session connection.
3. The servlet returns the checked-out connection by calling the checkIn( ) method. In many respects, the cached connection strategy is very similar in implementation in the servlet to the per-transaction strategy, except this time, we've reduced the cost of opening and closing connections by reusing them.
Example 4-9. A cached connection servlet
import java.io.*; import java.sql.*; import javax.servlet.*; import javax.servlet.http.*;
public class CachedConnectionServlet extends HttpServlet {
public void doGet(
HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
response.setContentType("text/html"); PrintWriter out = response.getWriter( ); out.println("<html>");
out.println("<head>");
out.println("<title>Cached Connection Servlet</title>"); out.println("</head>");
out.println("<body>");
// Turn on verbose output
CacheConnection.setVerbose(true);
// Get a cached connection
Connection connection = CacheConnection.checkOut( );
Statement statement = null; ResultSet resultSet = null; String userName = null; try {
// Test the connection
statement = connection.createStatement( ); resultSet = statement.executeQuery(
"select initcap(user) from sys.dual"); if (resultSet.next( ))
} catch (SQLException e) { out.println("DedicatedConnection.doGet( ) SQLException: " + e.getMessage( ) + "<p>"); } finally { if (resultSet != null)
try { resultSet.close( ); } catch (S QLException ignore) { } if (statement != null)
try { statement.close( ); } catch (SQLException ignore) { } }
// Return the conection
CacheConnection.checkIn(connection);
out.println("Hello " + userName + "!<p>");
out.println("You're using a cached connection!<p>"); out.println("</body>");
out.println("</html>"); }
public void doPost(
HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
doGet(request, response); }
}
This last connection strategy provides the response time efficiency of session connections, while at the same time reduces the number of simultaneous database connections to an on-demand minimum. In practice, I've seen a caching implementation like this handle a web site with more than 1,000 hits a day without ever having more than two simultaneous connections open. Now that's a drastic improvement in the number of simultaneous connections used when compared to the other three strategies.
Unfortunately, this strategy does not enable you to create transactions that span more than one
doXXX( ) method invocation. The reason you can't create such transactions is that you have no guarantee of getting the same connection object from one doXXX( ) method call to the next. So of the four connection strategies I've discussed in this chapter, which method should you choose? Let's discuss that next.