📄 connectionpool.java
字号:
// if you haven't properly set up the pool, then you can't start the pool!
if ((!this.classDriverSet)||(this.dbURL == null))
throw new SQLException("Sorry, you have to set the class driver and database URL before starting the pool");
connections = new Hashtable(); // build an empty connection matrix
// Put our pool of Connections in the Hashtable
for (int i = 0; i < initialConnections; i++)
connections.put(DriverManager.getConnection(dbURL, user, password), new ConData());
timer = new Timer(true); // the true means "Kill the timer if the parent (the pool) thread dies"
timer.schedule((new ConnectionPoolTimer(this, DEBUG)), iterationTime, iterationTime*1000*60); // pass in our DEBUG flag to the timertask
}
/**
* destroys the pool - <B>USE WITH CAUTION</B>
* <P>
* if you destroy a pool and want to use it again, you <B>MUST</B>
* call the <code>start()</code> method again to restart the pool before trying to get a connection from it!
* <P>
* Why use this? In case you want to use it in a <code>finalize()</code> method,
* or if you want to insure that you have closed all the connections
* in the pool for some othe reason - this will do it! <p> NOTE: If you want to
* make your pool available for garbage collection, it would be a good idea to
* call <code>destroy()</code> before setting your pool object to <code>null</code>.
* <P>
* @throws SQLException if it does not successfully destroy the connection pool
*/
public void destroy() throws SQLException
{
boolean successfulDestroy = true;
Enumeration cons = connections.keys();
Connection con = null;
synchronized (connections) {
while (cons.hasMoreElements())
{
con = (Connection)cons.nextElement();
if ( !destroyConnection(con) ) successfulDestroy = false;
}
}
connections = null;
timer.cancel(); // lets kill the responsible timer
timer = null; // and make it garbage collectable
if (!successfulDestroy) throw new SQLException("Did not successfully destroy all connections in " + this.getClass().getName() + "! Database may be unstable!");
}
/**
* Returns a String containing a report of the current pool's status <P>
* This can be useful if you want an "administrative view" of what's going on inside the connection pool. It can
* also be useful if you are sharing your pool with several programs, and someone isn't returning a connection - this
* might give you some idea of the problem program.<P>
* <table bgcolor="#DDDDDD" border="1" cellpadding="4" cellspacing="0"><tr><td>
* <B>The report looks like this (customized with your pool's size, etc):</B>
* <code><pre>
* Current Time: Mon Feb 25 13:02:06 PST 2002
* Current Size: 4
* Initial Size: 3
* - IN USE by UNIDENTIFIED (Mon Feb 25 13:02:06 PST 2002)
* - IN USE by TEST PROGRAM (Mon Feb 25 13:02:06 PST 2002)
* - IN USE by UNIDENTIFIED (Mon Feb 25 13:02:06 PST 2002)
* + FREE last used on Mon Feb 25 13:02:06 PST 2002
* </pre></code></td></tr></table><P>
* @return report of the current pool status
*/
public String getReport ()
{
java.util.Date curTime = new java.util.Date();
String returnString = NEWLINE + "Current Time: " + (new java.util.Date()) + NEWLINE;
returnString += "Current Size: " + connections.size() + NEWLINE;
returnString += "Initial Size: " + this.initialConnections + NEWLINE;
Enumeration cons = connections.keys();
Connection con = null;
synchronized (connections) { // sync this for no thread collision
while (cons.hasMoreElements()) // let's step through our connections
{
con = (Connection)cons.nextElement();
//let's see if there is a free connection that is over its age limit and over the initial size
if ((((ConData)connections.get(con)).inUse == false) && ((curTime.getTime() - ((ConData)connections.get(con)).lastUsed.getTime()) > (1*1000*60* minutesIdle )) && (connections.size() > initialConnections))
{
if (DEBUG) {
System.out.println("AGE: " + (curTime.getTime() - ((ConData)connections.get(con)).lastUsed.getTime()));
System.out.println("AGE: " + curTime + "," + ((ConData)connections.get(con)).lastUsed);
}
destroyConnection(con); // free up excess idle connections
}
// otherwise, let's see if it is free and healthy
else if (((ConData)connections.get(con)).inUse == false)
{
// So we found an unused connection.
try {
exerciseConnection(con); // make sure it's healthy
}
catch (SQLException e) {
// It's unhealthy - let's replace it.
try {
con = DriverManager.getConnection(dbURL, user, password);
}
catch (SQLException newConError) { // shouldn't ever get here, but if so...uh oh! database went down?
System.err.println("CRITICAL ERROR: " + newConError.getMessage() );
}
}
returnString += " + FREE last used on " + ((ConData)connections.get(con)).lastUsed + NEWLINE;
}
// otherwise, this connection must be checked out (in use) by something
else
{
returnString += " - IN USE by " + ((ConData)connections.get(con)).usedBy;
returnString += " (" + ((ConData)connections.get(con)).lastUsed + ")" + NEWLINE;
}
}
}
return (returnString);
}
/**
* Set how many minutes that a connection waits idle before being closed and removed from the pool.<P>
* Connections will only be removed from the pool if there are more than the number found in <code>setInitialConnection()</code> <P>
* @param timeInMinutes time (in munites) to keep an idle connection alive before closing it
*/
public void setMinutesIdle(int timeInMinutes) { this.minutesIdle = timeInMinutes; }
/**
* Set how many minutes to wait before the pool makes an integrity check of itself.<P>
* The pool will, every <code>timeInMinutes</code> minutes, check all the connections in
* the pool to make sure they are still doing ok. If an unhealthy connection is found,
* it is closed and a new one is opened in its place.<BR>
* <P>
* Note that once <code>start()</code> has been issued, this method will not affect your
* pool until you <code>destroy()</code> it and then restart it with <code>start()</code>
* <P>
* @param timeInMinutes time (in munites) between connection integrity checks.
* @see #start
* @see #destroy
*/
public void setHealthCheckIterationTime(int timeInMinutes) { this.iterationTime = timeInMinutes; }
/**
* Set the number of connections to initially open in the pool.<P>
* The pool will never have fewer than this many connections established to the DB. <P>
* @param initialConnections number of connections to open initially.
*/
public void setInitialConnections(int initialConnections) { this.initialConnections = initialConnections; }
/**
* Set the number of connections to open if none are available in the pool.<P>
* If you try and get a connection from the pool but there are none available,
* then the pool will open <code>incrementCount</code> new connections. These
* "extra" connections will be closed over time if they remain idle. <P>
* @param increment number of new connections to open
*/
public void setIncrementCount(int incrementCount) { this.increment = incrementCount; }
/**
* Set the password for the database user<P>
* @param password for the user given in setUser()
*/
public void setPassword(String password) { this.password = password; }
/**
* Set the URL to the database<P>
* Usually takes the form of <code>jdbc:<type>://<ip>:<port>/<database></code><P>
* @param dbURL JDBC url of the DB
*/
public void setURL(String databaseURL) { this.dbURL = databaseURL; } // usually takes the form: jdbc:<type>://<ip>:<port>/<database>
/**
* Set username for the database<P>
* @param username username to use when creating connections to database
*/
public void setUser(String username) { this.user = username; }
/**
* Set the JDBC Driver Class Name.<P>
* Note that the driver must be in your classpath. Refer to the docs that came with your JDBC driver for the specifics.<P>
* An example: <code>sun.jdbc.odbc.JdbcOdbcDriver</code><P>
* @param driverClassName JDBC Driver Class Name
* @throws ClassNotFoundException if the driver is not found in the classpath
*/
public void setDriverClassName(String driverClassName) throws ClassNotFoundException
{
Class.forName(driverClassName);
this.classDriverSet = true; // let the pool know that it's set successfully
}
///////////// PRIVATE METHODS BELOW
// This will return a connection to the pool AND DESTROY IT from the pool
private boolean destroyConnection (Connection toDestroy)
{
boolean successfulDestroy = false;
try
{
toDestroy.close();
connections.remove(toDestroy);
successfulDestroy = true; // must have worked if we got here
if (DEBUG) System.out.println("Destroyed a connection");
}
catch (SQLException e) {} // shouldn't get here - we'll throw it out and make it null - the DB will close it after a while
toDestroy = null; // make it available for garbage collection
return (successfulDestroy);
}
// this will insure a connection's health by running an arbitrary query on the connection
// it will throw an exception if the connection isn't healthy
private void exerciseConnection(Connection con) throws SQLException
{
if (!PRODUCTION)
{
//this is the simple way to do a health check - see below for a better way
con.setAutoCommit(true);
}
else
{
// this might be better for a production environment
Statement statmt;
ResultSet res;
statmt = con.createStatement();
String statement = "show tables"; // some dumb query that should work everywhere
res = statmt.executeQuery(statement);
res.close();
statmt.close();
}
} // end exerciseConnection
////////////////// INNER CLASSES
/**
* This inner class encapsulates a connection's information. It is used to
* internally represet if a connection is in use, and by whom it is being used
*/
private class ConData
{
private java.util.Date lastUsed = null;
private boolean inUse = false;
private String usedBy = "UNIDENTIFIED";
/**
* create a new condata object
* @param inUse is this connection in use?
* @param usedBy who is using this connection?
*/
public ConData (boolean inUse, String usedBy)
{
this.lastUsed = new java.util.Date();
this.inUse = inUse;
if (usedBy != null)
this.usedBy = usedBy;
}
/** create a new blank condata object */
public ConData ()
{
this(false,null);
}
} // end innerclass ConData
/** This inner class is a timertask that monitors the health and age of the connection objects.
* If one gets sick, then this will reissue a new one in its place.
* If a connection that was built to satisfy a request has outlived it's usefullness and is
* causing the connection pool to exceed the desired size, then the timer will kill it off.
*/
private class ConnectionPoolTimer extends TimerTask
{
private ConnectionPool self = null;
/** timer to keep track of connection pool health */
public ConnectionPoolTimer (ConnectionPool parentPool, boolean DEBUG)
{
this.self = parentPool;
}
/** thread killer - this should only happen if the main pool is already dead. */
public boolean cancel()
{
try
{ self.destroy(); } // try and have parentPool destroy itself
catch (Exception e) {} // throw it out
return super.cancel();
}
/**
* call the getReport() method to finger all of the connections for health
*/
public void run () // used for timer instance
{
// returns a string, but we don't care (unless we're debugging)
// we just call the report because it exercises the connections for us
String report = self.getReport();
if(DEBUG)System.out.println("THREAD DEBUG ------ " + NEWLINE + report);
}
} // end innerclass ConnectionPoolTimer
} // EOF
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -