Connection PoolsA web application that has been deployed on a server may have many clients accessing it. If each time the database connection is needed it has to be reopened, performance will degenerate quickly. The standard solution is to create a pool of connections that can be used when needed and then returned to the pool for use by other servlets.
The pool is stored in the ServletContext object so that all servlets in the application can use it to get connections. A special servlet called ConnectionServlet is needed to create the pool. It can also be used to display some information about the usage. The web deployment file, web.xml, contains along with the servlet name and class, <load-on-startup>1</load-on-startup>. This assures that the connection pool will be created before anything else is done.
The pool is implemented as a vector of ConnectionObjects. These objects not only contain the connection, but also some useful information about it. They record the number of times the connection was used, the last time it was accessed and whether or not it is currently in use.
The vector is stored in a class called ConnectionPool. This class has methods to create the connection pool, destroy it when no longer needed, get a connection from the pool and return a connection to the pool after it is no longer needed by the servlet that was using it. Some connection information is stored in a configuration file called ConnectionPool.cfg. This way the ConnectionPool class is not tied to any one database. Changing a few lines in the configuration file changes the target database.
Servlets that use the connection pool only require a little code in order to get the pool from the ServletContext and then obtain a connection. The main work of getting connections is done by the ConnectionPool class.
The code for this connection pool was adapted from that found in Java Servlets, Developers Guide by Karl Moss. Parts of it can be found on pages 226 to 249. The complete code is available on Karl Moss’ website, http://www.servletguru.com.
The ConnectionObject
The ConnectionObject class stores a connection and some data related to it. It uses a Date object to store information about the last access time. The code follows:
package produce;
import java.sql.*;
import java.io.*;
// There is a Date object in both java.sql and java.util. The
compiler must be told which one to use.
import java.util.Date;
public class ConnectionObject
{
private Connection con;
private int useCount;
private Date lastAccessTime;
private boolean inUse;
public Connection getConnection () {return
con;}
public int getUseCount () {return useCount;}
public Date getLastAccessTime () {return lastAccessTime;}
public boolean getInUse () {return inUse;}
public void setConnection (Connection c) {con
= c;}
public void setUseCount (int u) {useCount
= u;}
public void setLastAccessTime (Date d) {lastAccessTime
= d;}
public void setInUse (boolean i) {inUse =
i;}
public boolean isAvailable () {return !inUse;}
} // ConnectionObject
The ConnectionServlet Class
The ConnectionServlet class stores the connection pool in the ServletContext. It can then be accessed by any servlet in the web application. Like any servlet, it has either a doGet or a doPost method. But in this case, its main use is not to respond to a request. Instead it instantiates the connection pool and stores it in the ServletContext. Code for the init and destroy methods follow. The init method is automatically run when the servlet is loaded and the destroy method when it the application ends.
ConnectionPool pool;
public static String PoolKey = "produce.ConnectionServlet";
// The init method gets a new pool and initializes it.
public void init (ServletConfig cfg)
{
try
{
super.init
(cfg);
pool =
new ConnectionPool ();
pool.initialize
();
} catch (ServletException e) {System.out.println
("Could not initialize");}
ServletContext context = getServletContext
();
context.setAttribute (PoolKey, pool);
// Store the pool in the ServletContext.
} // init
/* The destroy method closes all connections in the pool and removes
the attribute from the ServletContext. */
public void destroy ()
{
getServletContext ().removeAttribute
(PoolKey);
if (pool != null) pool.destroy ();
super.destroy ();
} // destroy
A Servlet that Uses the Pool
Before going into details about how the ConnectionPool class works, we can see just how a servlet uses the ServletContext to get a connection. The only code needed gets the pool from the ServletContext and then gets a connection. (The full code for this servlet is at the end of the document.)
ServletContext context = getServletContext ();
String key = ConnectionServlet.PoolKey;
Object object = context.getAttribute (key);
if (object == null) out.println ("No connection pool.");
else
{
ConnectionPool pool = (ConnectionPool)
object;
Connection con = pool.getConnection
();
// Use the connection to access the
database.
pool.close (con);
}
The rest of the servlet is the same as before, except that it no longer throws a ClassNotFoundException. That is handled by the ConnectionPool class.
The Configuration File
ConnectionPool.cfg contains some connection information needed to create the connection. The file can be easily changed to refer to a different database. This makes the ConnectionPool class more flexible.
# ConnectionPool.cfg
# Defines connection pool parameters
JDBCDriver=sun.jdbc.odbc.JdbcOdbcDriver
JDBCConnectionURL=jdbc:odbc:produce
ConnectionPoolSize=5
ConnectionPoolMax=50
ConnectionUseCount=5
The configuration file can be loaded into a Properties class and from there into the ConnectionPool class. Changing the JDBCDriver or the JDBCConnectionURL will change the target database.
The ConnectionPool Class
The ConnectionPool class is the most complicated. It not only creates the vector of ConnectionObjects, but it also has methods to manage these objects.
package produce;
import java.sql.*;
import java.io.*;
// There is a Date object in java.sql, so the specific Data object
required is specifically imported here.
import java.util.Date;
import java.util.Vector;
import java.util.Properties;
/* The ConnectionPool class manages a pool of database connections. It reads configuration information from ConnectionPool.cfg and uses this to create a connection to the database. It also has methods that get a connection, get the entire pool, close a connection by returning it to the pool, and destroy the pool.
public class ConnectionPool
{
private String JDBCDriver;
private String JDBCConnectionURL;
private int ConnectionPoolSize;
private int ConnectionPoolMax;
private int ConnectionUseCount;
private Vector pool;
// The initialize method is called by the init
method of the ConnectionServlet class.
public void initialize()
{
String config
= "ConnectionPool.cfg";
boolean loaded
= loadConfig (config);
if (!loaded)
System.out.println ("Error loading config file.");
else
{
pool = new Vector ();
fillPool (ConnectionPoolSize);
}
} // initialize
public Vector getConnectionPoolObjects () {return pool;}
/* The fillPool method gets a connection and
then checks to see how many connections the database can support.
If it is less than ConnectionPoolSize, the pool size is adjusted.
It then fills the vector with new ConnectionObjects. */
private synchronized void fillPool (int poolSize)
{
try
{
Connection con; int count = 0;
int maxConnections = 0;
while (count < poolSize)
{
ConnectionObject conObject = new ConnectionObject ();
Class.forName (JDBCDriver);
con = DriverManager.getConnection (JDBCConnectionURL);
conObject.setConnection (con);
/* The first time through the loop, find out the maximum number of connections
that the database can support. */
if (count == 0)
{
con = conObject.getConnection ();
DatabaseMetaData metaData = con.getMetaData ();
maxConnections = metaData.getMaxConnections ();
if (poolSize > maxConnections)
{
System.out.println ("Pool size too large.");
poolSize = maxConnections;
}
}
conObject.setInUse (false); // Mark the connection as available.
conObject.setUseCount (0); // Start the use count off at 0.
conObject.setLastAccessTime (new Date ()); // Set the first access time.
pool.addElement (conObject); // Add the object to the vector.
count ++;
}
} catch (SQLException
e) {System.out.println ("SQLException");}
catch (ClassNotFoundException
e){System.out.println ("Class Not Found exception.\n");}
} // fillPool
// The method getConnection is synchronized
so that only one servlet can get a connection at a time.
public synchronized Connection getConnection
()
{
Connection con
= null;
ConnectionObject
connectionObject = null, conObject = null;
int poolSize
= pool.size (), count = 0;
boolean found
= false;
if (pool ==
null) return null; // Do not access an empty pool.
// Find the
first available connection in the pool.
while ((count
< poolSize) && !found)
{
conObject = (ConnectionObject) pool.elementAt (count);
if (conObject.isAvailable ()) found = true;
else count ++;
}
if (found) connectionObject
= conObject;
if (connectionObject
== null) System.out.println ("All connections in use.");
else
{
connectionObject.setInUse (true); // Make the connection unavailable to
others.
int useCount = connectionObject.getUseCount () + 1;
connectionObject.setUseCount (useCount); // Increment the use count.
connectionObject.setLastAccessTime (new Date ()); // Change the access
date.
con = connectionObject.getConnection ();
}
return con;
} // getConnection
// The close method makes the connection available
again. It does not really close the connection.
public synchronized void close (Connection
con)
{
int index =
find (con);
if (index !=
-1)
{
ConnectionObject conObject = (ConnectionObject) pool.elementAt (index);
conObject.setInUse (false);
conObject.setLastAccessTime (new Date ());
}
else System.out.println
("Connection not found in pool.");
} // close
/* find is a private method that searches through
the vector to find a connection with the same catalog name.
When one is found, its index
is returned to the close method. */
private int find (Connection con)
{
int index =
0;
boolean found
= false;
try
{
ConnectionObject conObject;
String catalog = con.getCatalog ();
while ((index < pool.size ()) && !found)
{
conObject = (ConnectionObject) pool.elementAt (index);
Connection poolCon = conObject.getConnection ();
String name = poolCon.getCatalog ();
if (catalog.equals (name)) found = true;
else index ++;
}
} catch (SQLException
e) {System.out.println ("Catalog Exception");}
if (found) return
index;
else return
-1;
} // find
/* The destroy method is executed when the
application is finished.
It closes each connection in the pool and then sets
the pool to null. */
public void destroy ()
{
try
{
if (pool != null)
{
// Close each connection in the pool.
for (int count = 0; count < pool.size(); count++)
{
ConnectionObject co = (ConnectionObject) pool.elementAt(count);
Connection con = co.getConnection ();
con.close (); // This really closes the connection.
}
}
} catch (SQLException
e) {System.out.println ("Destroy error.");}
pool = null;
} // destroy
/* The loadConfig method is used to read in
the configuration file and store its data in the class variables. */
private boolean loadConfig (String config)
{
InputStream
in = null;
boolean loaded
= false;
try
{
ClassLoader loader = getClass().getClassLoader(); // Get the loader for
this class.
if (loader != null) in = loader.getResourceAsStream(config);
else in = ClassLoader.getSystemResourceAsStream(config);
// If the input stream is null, then the configuration file was not found.
if (in == null)
System.out.println ("ConnectionPool configuration file, '"+ config + "',
not found");
else
{
Properties JDBCProperties = new Properties();
// Load the configuration file into the properties table
JDBCProperties.load(in);
JDBCDriver = JDBCProperties.getProperty ("JDBCDriver");
JDBCConnectionURL = JDBCProperties.getProperty ("JDBCConnectionURL");
ConnectionPoolSize = Integer.parseInt (JDBCProperties.getProperty ("ConnectionPoolSize"));
ConnectionPoolMax = Integer.parseInt (JDBCProperties.getProperty
("ConnectionPoolMax"));
ConnectionUseCount = Integer.parseInt (JDBCProperties.getProperty ("ConnectionUseCount"));
loaded = true;
}
} catch (IOException
e) {System.out.println ("IOException");}
finally
{if (in != null) try {in.close();}catch (IOException ex) {}} // Close the
input stream.
return loaded;
} // loadConfig
} // ConnectionPool
The FindServlet
The servlet, FindServlet, uses the connection pool to get a new connection. The full code follows:
/* FindServlet is a Java program that gets a key id from the request,
finds the product with the id
and displays the product's data on the output page.*/
package produce;
import java.sql.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
/* FindServlet finds a specific product and displays the data
on the output page. */
public class FindServlet extends HttpServlet
{
public void doGet (HttpServletRequest request,
HttpServletResponse response)
{
PrintWriter
out = null;
ConnectionPool
pool;
Connection con;
try
{
out = response.getWriter ();
Page.createHeader (out, "Find a Product");
ServletContext context = getServletContext ();
String key = ConnectionServlet.PoolKey;
Object object = context.getAttribute (key);
if (object == null) out.println ("No connection pool.");
else
{
pool = (ConnectionPool) object;
con = pool.getConnection ();
String keyName = request.getParameter ("keyName");
boolean found = findProduct (con, out, keyName);
if (!found) out.println ("Product not in database.");
Page.createFooter (out);
pool.close (con);
}
} catch (IOException
ex) {out.println ("<h3>IO Exception.</h3>");}
} // doGet
private boolean findProduct (Connection con,
PrintWriter out, String keyName)
{
boolean found
= false;
try
{
Statement stmt = con.createStatement ();
String query = "SELECT * FROM ProduceTable Where Name = '" + keyName +
"'";
ResultSet rs = stmt.executeQuery (query);
if (rs.next ())
{
found = true;
ResultSetMetaData metaData = rs.getMetaData ();
int columns = metaData.getColumnCount ();
out.println ("<h3>Produce Table</h3>");
out.println ("<table border='1' bordercolor='#000000' cellspacing='10'><tr>");
for (int count = 1; count <= columns; count ++)
out.println ("<td>"+metaData.getColumnName (count)+"</td>");
out.println ("</tr><tr>");
for (int count = 1; count <= columns; count ++)
out.println ("<td>"+rs.getString (count)+"</td>");
out.println ("</tr></table>");
}
} catch (SQLException
es) {out.println ("SQL Find Exception");}
return found;
} // findProduct
} // class FindServlet