Java/Database SQL JDBC/Connection Pool
Содержание
A common intermediate format for a non-XA JDBC pool
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.Serializable;
import java.util.Properties;
/**
* A common intermediate format for a non-XA JDBC pool
*
* @version $Rev: 476049 $ $Date: 2006-11-16 20:35:17 -0800 (Thu, 16 Nov 2006) $
*/
public class JDBCPool extends AbstractDatabasePool {
private String driverClass;
private String jdbcURL;
private String username;
private String password;
private Properties connectionProperties = new Properties();
public String getDriverClass() {
return driverClass;
}
public void setDriverClass(String driverClass) {
this.driverClass = driverClass;
}
public String getJdbcURL() {
return jdbcURL;
}
public void setJdbcURL(String jdbcURL) {
this.jdbcURL = jdbcURL;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Properties getConnectionProperties() {
return connectionProperties;
}
public void setConnectionProperties(Properties connectionProperties) {
this.connectionProperties = connectionProperties;
}
}
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* A common intermediate format for a database connection pool
*
* @version $Rev: 476049 $ $Date: 2006-11-16 20:35:17 -0800 (Thu, 16 Nov 2006) $
*/
abstract class AbstractDatabasePool implements Serializable {
public final static String VENDOR_ORACLE = "Oracle";
public final static String VENDOR_MYSQL = "MySQL";
public final static String VENDOR_SYBASE = "Sybase";
public final static String VENDOR_INFORMIX = "Informix";
private String name;
private String jndiName;
private Integer minSize;
private Integer maxSize;
private Integer blockingTimeoutMillis;
private Integer idleTimeoutMillis;
private String newConnectionSQL;
private String testConnectionSQL;
private String vendor;
private Integer statementCacheSize;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getJndiName() {
return jndiName;
}
public void setJndiName(String jndiName) {
this.jndiName = jndiName;
}
public String getNewConnectionSQL() {
return newConnectionSQL;
}
public void setNewConnectionSQL(String newConnectionSQL) {
this.newConnectionSQL = newConnectionSQL;
}
public String getTestConnectionSQL() {
return testConnectionSQL;
}
public void setTestConnectionSQL(String testConnectionSQL) {
this.testConnectionSQL = testConnectionSQL;
}
public String getVendor() {
return vendor;
}
public void setVendor(String vendor) {
this.vendor = vendor;
}
public Integer getMinSize() {
return minSize;
}
public void setMinSize(Integer minSize) {
this.minSize = minSize;
}
public Integer getMaxSize() {
return maxSize;
}
public void setMaxSize(Integer maxSize) {
this.maxSize = maxSize;
}
public Integer getBlockingTimeoutMillis() {
return blockingTimeoutMillis;
}
public void setBlockingTimeoutMillis(Integer blockingTimeoutMillis) {
this.blockingTimeoutMillis = blockingTimeoutMillis;
}
public Integer getIdleTimeoutMillis() {
return idleTimeoutMillis;
}
public void setIdleTimeoutMillis(Integer idleTimeoutMillis) {
this.idleTimeoutMillis = idleTimeoutMillis;
}
public Integer getStatementCacheSize() {
return statementCacheSize;
}
public void setStatementCacheSize(Integer statementCacheSize) {
this.statementCacheSize = statementCacheSize;
}
}
Mini Connection Pool Manager
// Test program for the MiniConnectionPoolManager class.
import java.io.PrintWriter;
import java.lang.Thread;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Random;
import javax.sql.ConnectionPoolDataSource;
import biz.source_code.miniConnectionPoolManager.MiniConnectionPoolManager;
public class TestMiniConnectionPoolManager {
private static final int maxConnections = 8; // number of connections
private static final int noOfThreads = 50; // number of worker threads
private static final int processingTime = 30; // total processing time of the test program in seconds
private static final int threadPauseTime1 = 100; // max. thread pause time in microseconds, without a connection
private static final int threadPauseTime2 = 100; // max. thread pause time in microseconds, with a connection
private static MiniConnectionPoolManager poolMgr;
private static WorkerThread[] threads;
private static boolean shutdownFlag;
private static Object shutdownObj = new Object();
private static Random random = new Random();
private static class WorkerThread extends Thread {
public int threadNo;
public void run() {threadMain (threadNo); }};
private static ConnectionPoolDataSource createDataSource() throws Exception {
// Version for H2:
org.h2.jdbcx.JdbcDataSource dataSource = new org.h2.jdbcx.JdbcDataSource();
dataSource.setURL ("jdbc:h2:file:c:/temp/temp_TestMiniConnectionPoolManagerDB;DB_CLOSE_DELAY=-1");
// Version for Apache Derby:
/*
org.apache.derby.jdbc.EmbeddedConnectionPoolDataSource dataSource = new org.apache.derby.jdbc.EmbeddedConnectionPoolDataSource();
dataSource.setDatabaseName ("c:/temp/temp_TestMiniConnectionPoolManagerDB");
dataSource.setCreateDatabase ("create");
dataSource.setLogWriter (new PrintWriter(System.out));
*/
// Versioo for JTDS:
/*
net.sourceforge.jtds.jdbcx.JtdsDataSource dataSource = new net.sourceforge.jtds.jdbcx.JtdsDataSource();
dataSource.setAppName ("TestMiniConnectionPoolManager");
dataSource.setDatabaseName ("Northwind");
dataSource.setServerName ("localhost");
dataSource.setUser ("sa");
dataSource.setPassword (System.getProperty("saPassword"));
*/
// Version for the Microsoft SQL Server driver (sqljdbc.jar):
/*
// The sqljdbc 1.1 documentation, chapter "Using Connection Pooling", recommends to use
// SQLServerXADataSource instead of SQLServerConnectionPoolDataSource, even when no
// distributed transactions are used.
com.microsoft.sqlserver.jdbc.SQLServerXADataSource dataSource = new com.microsoft.sqlserver.jdbc.SQLServerXADataSource();
dataSource.setApplicationName ("TestMiniConnectionPoolManager");
dataSource.setDatabaseName ("Northwind");
dataSource.setServerName ("localhost");
dataSource.setUser ("sa");
dataSource.setPassword (System.getProperty("saPassword"));
dataSource.setLogWriter (new PrintWriter(System.out));
*/
return dataSource; }
public static void main (String[] args) throws Exception {
System.out.println ("Program started.");
ConnectionPoolDataSource dataSource = createDataSource();
poolMgr = new MiniConnectionPoolManager(dataSource,maxConnections);
initDb();
startWorkerThreads();
pause (processingTime*1000000);
System.out.println ("\nStopping threads.");
stopWorkerThreads();
System.out.println ("\nAll threads stopped.");
poolMgr.dispose();
System.out.println ("Program completed."); }
private static void startWorkerThreads() {
threads = new WorkerThread[noOfThreads];
for (int threadNo=0; threadNo<noOfThreads; threadNo++) {
WorkerThread thread = new WorkerThread();
threads[threadNo] = thread;
thread.threadNo = threadNo;
thread.start(); }}
private static void stopWorkerThreads() throws Exception {
setShutdownFlag();
for (int threadNo=0; threadNo<noOfThreads; threadNo++) {
threads[threadNo].join(); }}
private static void setShutdownFlag() {
synchronized (shutdownObj) {
shutdownFlag = true;
shutdownObj.notifyAll(); }}
private static void threadMain (int threadNo) {
try {
threadMain2 (threadNo); }
catch (Throwable e) {
System.out.println ("\nException in thread "+threadNo+": "+e);
e.printStackTrace (System.out);
setShutdownFlag(); }}
private static void threadMain2 (int threadNo) throws Exception {
// System.out.println ("Thread "+threadNo+" started.");
while (true) {
if (!pauseRandom(threadPauseTime1)) return;
threadTask (threadNo); }}
private static void threadTask (int threadNo) throws Exception {
Connection conn = null;
try {
conn = poolMgr.getConnection();
if (shutdownFlag) return;
System.out.print (threadNo+" ");
incrementThreadCounter (conn,threadNo);
pauseRandom (threadPauseTime2); }
finally {
if (conn != null) conn.close(); }}
private static boolean pauseRandom (int maxPauseTime) throws Exception {
return pause (random.nextInt(maxPauseTime)); }
private static boolean pause (int pauseTime) throws Exception {
synchronized (shutdownObj) {
if (shutdownFlag) return false;
if (pauseTime <= 0) return true;
int ms = pauseTime / 1000;
int ns = (pauseTime % 1000) * 1000;
shutdownObj.wait (ms,ns); }
return true; }
private static void initDb() throws SQLException {
Connection conn = null;
try {
conn = poolMgr.getConnection();
System.out.println ("initDb connected");
initDb2 (conn); }
finally {
if (conn != null) conn.close(); }
System.out.println ("initDb done"); }
private static void initDb2 (Connection conn) throws SQLException {
execSqlNoErr (conn,"drop table temp");
execSql (conn,"create table temp (threadNo integer, ctr integer)");
for (int i=0; i<noOfThreads; i++)
execSql (conn,"insert into temp values("+i+",0)"); }
private static void incrementThreadCounter (Connection conn, int threadNo) throws SQLException {
execSql (conn,"update temp set ctr = ctr + 1 where threadNo="+threadNo); }
private static void execSqlNoErr (Connection conn, String sql) {
try {
execSql (conn,sql); }
catch (SQLException e) {}}
private static void execSql (Connection conn, String sql) throws SQLException {
Statement st = null;
try {
st = conn.createStatement();
st.executeUpdate(sql); }
finally {
if (st != null) st.close(); }}
} // end class TestMiniConnectionPoolManager
// Copyright 2007 Christian d"Heureuse, www.source-code.biz
//
// This module is free software: you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version. See http://www.gnu.org/licenses/lgpl.html.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
package biz.source_code.miniConnectionPoolManager;
import java.util.concurrent.Semaphore;
import java.util.Stack;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.TimeUnit;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.ConnectionEvent;
import javax.sql.ConnectionEventListener;
import javax.sql.PooledConnection;
/**
* A simple standalone JDBC connection pool manager.
* <p>
* The public methods of this class are thread-safe.
* <p>
* Author: Christian d"Heureuse (
* <p>
* 2007-06-21: Constructor with a timeout parameter added.
*/
public class MiniConnectionPoolManager {
private ConnectionPoolDataSource dataSource;
private int maxConnections;
private int timeout;
private PrintWriter logWriter;
private Semaphore semaphore;
private Stack<PooledConnection> recycledConnections;
private int activeConnections;
private PoolConnectionEventListener poolConnectionEventListener;
private boolean isDisposed;
/**
* Thrown in {@link #getConnection()} when no free connection becomes available within <code>timeout</code> seconds.
*/
public static class TimeoutException extends RuntimeException {
private static final long serialVersionUID = 1;
public TimeoutException () {
super ("Timeout while waiting for a free database connection."); }}
/**
* Constructs a MiniConnectionPoolManager object with a timeout of 60 seconds.
* @param dataSource the data source for the connections.
* @param maxConnections the maximum number of connections.
*/
public MiniConnectionPoolManager (ConnectionPoolDataSource dataSource, int maxConnections) {
this (dataSource, maxConnections, 60); }
/**
* Constructs a MiniConnectionPoolManager object.
* @param dataSource the data source for the connections.
* @param maxConnections the maximum number of connections.
* @param timeout the maximum time in seconds to wait for a free connection.
*/
public MiniConnectionPoolManager (ConnectionPoolDataSource dataSource, int maxConnections, int timeout) {
this.dataSource = dataSource;
this.maxConnections = maxConnections;
this.timeout = timeout;
try {
logWriter = dataSource.getLogWriter(); }
catch (SQLException e) {}
if (maxConnections < 1) throw new IllegalArgumentException("Invalid maxConnections value.");
semaphore = new Semaphore(maxConnections,true);
recycledConnections = new Stack<PooledConnection>();
poolConnectionEventListener = new PoolConnectionEventListener(); }
/**
* Closes all unused pooled connections.
*/
public synchronized void dispose() throws SQLException {
if (isDisposed) return;
isDisposed = true;
SQLException e = null;
while (!recycledConnections.isEmpty()) {
PooledConnection pconn = recycledConnections.pop();
try {
pconn.close(); }
catch (SQLException e2) {
if (e == null) e = e2; }}
if (e != null) throw e; }
/**
* Retrieves a connection from the connection pool.
* If <code>maxConnections</code> connections are already in use, the method
* waits until a connection becomes available or <code>timeout</code> seconds elapsed.
* When the application is finished using the connection, it must close it
* in order to return it to the pool.
* @return a new Connection object.
* @throws TimeoutException when no connection becomes available within <code>timeout</code> seconds.
*/
public Connection getConnection() throws SQLException {
// This routine is unsynchronized, because semaphore.tryAcquire() may block.
synchronized (this) {
if (isDisposed) throw new IllegalStateException("Connection pool has been disposed."); }
try {
if (!semaphore.tryAcquire(timeout,TimeUnit.SECONDS))
throw new TimeoutException(); }
catch (InterruptedException e) {
throw new RuntimeException("Interrupted while waiting for a database connection.",e); }
boolean ok = false;
try {
Connection conn = getConnection2();
ok = true;
return conn; }
finally {
if (!ok) semaphore.release(); }}
private synchronized Connection getConnection2() throws SQLException {
if (isDisposed) throw new IllegalStateException("Connection pool has been disposed."); // test again with lock
PooledConnection pconn;
if (!recycledConnections.empty()) {
pconn = recycledConnections.pop(); }
else {
pconn = dataSource.getPooledConnection(); }
Connection conn = pconn.getConnection();
activeConnections++;
pconn.addConnectionEventListener (poolConnectionEventListener);
assertInnerState();
return conn; }
private synchronized void recycleConnection (PooledConnection pconn) {
if (isDisposed) { disposeConnection (pconn); return; }
if (activeConnections <= 0) throw new AssertionError();
activeConnections--;
semaphore.release();
recycledConnections.push (pconn);
assertInnerState(); }
private synchronized void disposeConnection (PooledConnection pconn) {
if (activeConnections <= 0) throw new AssertionError();
activeConnections--;
semaphore.release();
closeConnectionNoEx (pconn);
assertInnerState(); }
private void closeConnectionNoEx (PooledConnection pconn) {
try {
pconn.close(); }
catch (SQLException e) {
log ("Error while closing database connection: "+e.toString()); }}
private void log (String msg) {
String s = "MiniConnectionPoolManager: "+msg;
try {
if (logWriter == null)
System.err.println (s);
else
logWriter.println (s); }
catch (Exception e) {}}
private void assertInnerState() {
if (activeConnections < 0) throw new AssertionError();
if (activeConnections+recycledConnections.size() > maxConnections) throw new AssertionError();
if (activeConnections+semaphore.availablePermits() > maxConnections) throw new AssertionError(); }
private class PoolConnectionEventListener implements ConnectionEventListener {
public void connectionClosed (ConnectionEvent event) {
PooledConnection pconn = (PooledConnection)event.getSource();
pconn.removeConnectionEventListener (this);
recycleConnection (pconn); }
public void connectionErrorOccurred (ConnectionEvent event) {
PooledConnection pconn = (PooledConnection)event.getSource();
pconn.removeConnectionEventListener (this);
disposeConnection (pconn); }}
/**
* Returns the number of active (open) connections of this pool.
* This is the number of <code>Connection</code> objects that have been
* issued by {@link #getConnection()} for which <code>Connection.close()</code>
* has not yet been called.
* @return the number of active connections.
**/
public synchronized int getActiveConnections() {
return activeConnections; }
} // end class MiniConnectionPoolManager
Pooled Connection Example
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
public class MainClass {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = getConnection();
// Do work with connection
statement = connection.createStatement();
String selectEmployeesSQL = "SELECT * FROM employees";
resultSet = statement.executeQuery(selectEmployeesSQL);
while (resultSet.next()) {
printEmployee(resultSet);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
} // nothing we can do
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
} // nothing we can do
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
} // nothing we can do
}
}
}
private static Connection getConnection() throws NamingException, SQLException {
InitialContext initCtx = createContext();
String jndiName = "HrDS";
ConnectionPoolDataSource dataSource = (ConnectionPoolDataSource) initCtx.lookup(jndiName);
PooledConnection pooledConnection = dataSource.getPooledConnection();
return pooledConnection.getConnection(); // Obtain connection from pool
}
private static InitialContext createContext() throws NamingException {
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL, "rmi://localhost:1099");
InitialContext context = new InitialContext(env);
return context;
}
private static void printEmployee(ResultSet resultSet) throws SQLException {
System.out.print(resultSet.getInt("employee_id")+", ");
System.out.print(resultSet.getString("last_name")+", ");
System.out.print(resultSet.getString("first_name")+", ");
System.out.println(resultSet.getString("email"));
}
}
Your own connection pool
/*
* Copyright Aduna (http://www.aduna-software.ru/) (c) 1997-2006.
*
* Licensed under the Aduna BSD-style license.
*/
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
public class ConnectionPool {
/*--------------------------------------------------+
| Variables |
+--------------------------------------------------*/
protected List<PoolConnection> _connections;
protected String _url;
protected String _user;
protected String _password;
/**
* Indicates whether the ConnectionPool should check the status of
* connections (closed, has warnings) before they are returned.
**/
protected boolean _checkConnections = true;
protected long _cleaningInterval = 30 * 1000; // 30 seconds
protected long _maxIdleTime = 30 * 1000; // 30 seconds
protected long _maxUseTime = -1; // disabled by default
protected boolean _draining = false;
protected PoolCleaner _cleaner;
/*--------------------------------------------------+
| Constructors |
+--------------------------------------------------*/
public ConnectionPool(String url, String user, String password) {
_url = url;
_user = user;
_password = password;
_connections = new ArrayList<PoolConnection>();
}
/**
* Sets the flag that determines whether the the status of connections
* (closed, has warnings) is checked before they are returned by
* getConnection(). With some jdbc-drivers, the extra checks can have
* a large performance penalty. Default value is "true".
**/
public void setCheckConnections(boolean checkConnections) {
_checkConnections = checkConnections;
}
/**
* Sets the interval for the pool cleaner to come into action. The pool
* cleaner checks the connection pool every so many milliseconds for
* connections that should be removed. The default interval is 30 seconds.
* @param cleaningInterval The interval in milliseconds.
**/
public void setCleaningInterval(long cleaningInterval) {
_cleaningInterval = cleaningInterval;
}
/**
* Sets the maximum time that a connection is allowed to be idle. A
* connection that has been idle for a longer time will be removed
* by the pool cleaner the next time it check the pool. The default
* value is 30 seconds.
*
* @param maxIdleTime The maximum idle time in milliseconds.
**/
public void setMaxIdleTime(long maxIdleTime) {
_maxIdleTime = maxIdleTime;
}
/**
* Sets the maximum time that a connection is allowed to be used. A
* connection that has been used for a longer time will be forced to
* close itself, even if it is still in use. Normally, this time should
* only be reached in case an program "forgets" to close a connection.
* The maximum time is switched of by default.
*
* @param maxUseTime The maximum time a connection can be used in
* milliseconds, or a negative value if there is no maximum.
**/
public void setMaxUseTime(long maxUseTime) {
_maxUseTime = maxUseTime;
}
/*--------------------------------------------------+
| Methods |
+--------------------------------------------------*/
public Connection getConnection()
throws SQLException
{
if (_draining) {
throw new SQLException("ConnectionPool was drained.");
}
// Try reusing an existing Connection
synchronized (_connections) {
PoolConnection pc = null;
for (int i = 0; i < _connections.size(); i++) {
pc = _connections.get(i);
if (pc.lease()) {
// PoolConnection is available
if (!_checkConnections) {
return pc;
}
else {
// Check the status of the connection
boolean isHealthy = true;
try {
if (pc.isClosed() && pc.getWarnings() != null) {
// If something happend to the connection, we
// don"t want to use it anymore.
isHealthy = false;
}
}
catch(SQLException sqle) {
// If we can"t even ask for that information, we
// certainly don"t want to use it anymore.
isHealthy = false;
}
if (isHealthy) {
return pc;
}
else {
try {
pc.expire();
}
catch(SQLException sqle) {
// ignore
}
_connections.remove(i);
}
}
}
}
}
// Create a new Connection
Connection con = DriverManager.getConnection(_url, _user, _password);
PoolConnection pc = new PoolConnection(con);
pc.lease();
// Add it to the pool
synchronized (_connections) {
_connections.add(pc);
if (_cleaner == null) {
// Put a new PoolCleaner to work
_cleaner = new PoolCleaner(_cleaningInterval);
_cleaner.start();
}
}
return pc;
}
public void removeExpired() {
PoolConnection pc;
long maxIdleDeadline = System.currentTimeMillis() - _maxIdleTime;
long maxUseDeadline = System.currentTimeMillis() - _maxUseTime;
synchronized (_connections) {
// Check all connections
for (int i = _connections.size() - 1; i >= 0; i--) {
pc = _connections.get(i);
if (!pc.inUse() && pc.getTimeClosed() < maxIdleDeadline) {
// Connection has been idle too long, close it.
_connections.remove(i);
try {
pc.expire();
}
catch (SQLException ignore) {
}
}
else if (
_maxUseTime >= 0 && // don"t check if disabled
pc.inUse() &&
pc.getTimeOpened() < maxUseDeadline)
{
// Connection has been used too long, close it.
// Print the location where the connetion was acquired
// as it probably forgot to close the connection (which
// is a bug).
System.err.println("Warning: forced closing of a connection that has been in use too long.");
System.err.println("Connection was acquired in:");
pc.printStackTrace();
System.err.println();
_connections.remove(i);
try {
pc.expire();
}
catch (SQLException ignore) {
}
}
}
// Stop the PoolCleaner if the pool is empty.
if (_connections.size() == 0 && _cleaner != null) {
_cleaner.halt();
_cleaner = null;
}
}
}
public int getPoolSize() {
synchronized (_connections) {
return _connections.size();
}
}
/**
* Drains the pool. After the ConnectionPool has been drained it will not
* give out any more connections and all existing connections will be
* closed. This action cannot be reversed, so a ConnectionPool will become
* unusable once it has been drained.
**/
public void drain() {
_draining = true;
if (_cleaner != null) {
_cleaner.halt();
}
synchronized (_connections) {
for (int i = _connections.size() - 1; i >= 0; i--) {
PoolConnection pc = _connections.get(i);
if (pc.inUse()) {
System.err.println("Warning: forced closing of a connection still in use.");
System.err.println("Connection was acquired in:");
pc.printStackTrace();
System.err.println();
}
_connections.remove(i);
try {
pc.expire();
}
catch (SQLException ignore) {
}
}
}
}
protected void finalize() {
drain();
}
/*--------------------------------------------+
| inner class PoolConnection |
+--------------------------------------------*/
/**
* Wrapper around java.sql.Connection
**/
static class PoolConnection implements Connection {
/*----------------------------------+
| Variables |
+----------------------------------*/
protected Connection _conn;
protected boolean _inUse;
protected boolean _autoCommit;
/** Time stamp for the last time the connection was opened. **/
protected long _timeOpened;
/** Time stamp for the last time the connection was closed. **/
protected long _timeClosed;
private Throwable _throwable;
/*----------------------------------+
| Constructors |
+----------------------------------*/
public PoolConnection(Connection conn) {
_conn = conn;
_inUse = false;
_autoCommit = true;
}
/*----------------------------------+
| PoolConnection specific methods |
+----------------------------------*/
/**
* Tries to lease this connection. If the attempt was successful (the
* connection was available), a flag will be set marking this connection
* "in use", and this method will return "true". If the connection was
* already in use, this method will return "false".
**/
public synchronized boolean lease() {
if (_inUse) {
return false;
}
else {
_inUse = true;
_timeOpened = System.currentTimeMillis();
return true;
}
}
/**
* Checks if the connection currently is used by someone.
**/
public boolean inUse() {
return _inUse;
}
/**
* Returns the time stamp of the last time this connection was
* opened/leased.
**/
public synchronized long getTimeOpened() {
return _timeOpened;
}
/**
* Returns the time stamp of the last time this connection was
* closed.
**/
public synchronized long getTimeClosed() {
return _timeClosed;
}
/**
* Expires this connection and closes the underlying connection to the
* database. Once expired, a connection can no longer be used.
**/
public void expire()
throws SQLException
{
_conn.close();
_conn = null;
}
public void printStackTrace() {
_throwable.printStackTrace(System.err);
}
/*----------------------------------+
| Wrapping methods for Connection |
+----------------------------------*/
public synchronized void close()
throws SQLException
{
// Multiple calls to close?
if (_inUse) {
_timeClosed = System.currentTimeMillis();
_inUse = false;
if (_autoCommit == false) {
// autoCommit has been set to false by this user,
// restore the default "autoCommit = true"
setAutoCommit(true);
}
}
}
public Statement createStatement()
throws SQLException
{
_throwable = new Throwable();
return _conn.createStatement();
}
public PreparedStatement prepareStatement(String sql)
throws SQLException
{
_throwable = new Throwable();
return _conn.prepareStatement(sql);
}
public CallableStatement prepareCall(String sql)
throws SQLException
{
return _conn.prepareCall(sql);
}
public String nativeSQL(String sql)
throws SQLException
{
return _conn.nativeSQL(sql);
}
public void setAutoCommit(boolean autoCommit)
throws SQLException
{
_conn.setAutoCommit(autoCommit);
_autoCommit = _conn.getAutoCommit();
}
public boolean getAutoCommit()
throws SQLException
{
return _conn.getAutoCommit();
}
public void commit()
throws SQLException
{
_conn.rumit();
}
public void rollback()
throws SQLException
{
_conn.rollback();
}
public boolean isClosed()
throws SQLException
{
return _conn.isClosed();
}
public DatabaseMetaData getMetaData()
throws SQLException
{
return _conn.getMetaData();
}
public void setReadOnly(boolean readOnly)
throws SQLException
{
_conn.setReadOnly(readOnly);
}
public boolean isReadOnly()
throws SQLException
{
return _conn.isReadOnly();
}
public void setCatalog(String catalog)
throws SQLException
{
_conn.setCatalog(catalog);
}
public String getCatalog()
throws SQLException
{
return _conn.getCatalog();
}
public void setTransactionIsolation(int level)
throws SQLException
{
_conn.setTransactionIsolation(level);
}
public int getTransactionIsolation()
throws SQLException
{
return _conn.getTransactionIsolation();
}
public SQLWarning getWarnings()
throws SQLException
{
return _conn.getWarnings();
}
public void clearWarnings()
throws SQLException
{
_conn.clearWarnings();
}
public Statement createStatement(
int resultSetType, int resultSetConcurrency)
throws SQLException
{
return _conn.createStatement(resultSetType, resultSetConcurrency);
}
public PreparedStatement prepareStatement(
String sql, int resultSetType, int resultSetConcurrency)
throws SQLException
{
return _conn.prepareStatement(sql, resultSetType, resultSetConcurrency);
}
public CallableStatement prepareCall(
String sql, int resultSetType, int resultSetConcurrency)
throws SQLException
{
return _conn.prepareCall(sql, resultSetType, resultSetConcurrency);
}
public Map<String,Class<?>> getTypeMap()
throws SQLException
{
return _conn.getTypeMap();
}
public void setTypeMap(Map<String,Class<?>> map)
throws SQLException
{
_conn.setTypeMap(map);
}
/*
* The following methods are new methods from java.sql.Connection that
* were added in JDK1.4. These additions are incompatible with older JDK
* versions.
*/
public void setHoldability(int holdability)
throws SQLException
{
_conn.setHoldability(holdability);
}
public int getHoldability()
throws SQLException
{
return _conn.getHoldability();
}
public Savepoint setSavepoint()
throws SQLException
{
return _conn.setSavepoint();
}
public Savepoint setSavepoint(String name)
throws SQLException
{
return _conn.setSavepoint(name);
}
public void rollback(Savepoint savepoint)
throws SQLException
{
_conn.rollback(savepoint);
}
public void releaseSavepoint(Savepoint savepoint)
throws SQLException
{
_conn.releaseSavepoint(savepoint);
}
public Statement createStatement(
int resultSetType,
int resultSetConcurrency,
int resultSetHoldability)
throws SQLException
{
return _conn.createStatement(
resultSetType, resultSetConcurrency, resultSetHoldability);
}
public PreparedStatement prepareStatement(
String sql,
int resultSetType,
int resultSetConcurrency,
int resultSetHoldability)
throws SQLException
{
return _conn.prepareStatement(
sql, resultSetType, resultSetConcurrency, resultSetHoldability);
}
public CallableStatement prepareCall(
String sql,
int resultSetType,
int resultSetConcurrency,
int resultSetHoldability)
throws SQLException
{
return _conn.prepareCall(
sql, resultSetType, resultSetConcurrency, resultSetHoldability);
}
public PreparedStatement prepareStatement(
String sql, int autoGenerateKeys)
throws SQLException
{
return _conn.prepareStatement(sql, autoGenerateKeys);
}
public PreparedStatement prepareStatement(
String sql, int[] columnIndexes)
throws SQLException
{
return _conn.prepareStatement(sql, columnIndexes);
}
public PreparedStatement prepareStatement(
String sql, String[] columnNames)
throws SQLException
{
return _conn.prepareStatement(sql, columnNames);
}
public Clob createClob() throws SQLException {
// TODO Auto-generated method stub
return null;
}
public Blob createBlob() throws SQLException {
// TODO Auto-generated method stub
return null;
}
public NClob createNClob() throws SQLException {
// TODO Auto-generated method stub
return null;
}
public SQLXML createSQLXML() throws SQLException {
// TODO Auto-generated method stub
return null;
}
public boolean isValid(int timeout) throws SQLException {
// TODO Auto-generated method stub
return false;
}
public void setClientInfo(String name, String value) throws SQLClientInfoException {
// TODO Auto-generated method stub
}
public void setClientInfo(Properties properties) throws SQLClientInfoException {
// TODO Auto-generated method stub
}
public String getClientInfo(String name) throws SQLException {
// TODO Auto-generated method stub
return null;
}
public Properties getClientInfo() throws SQLException {
// TODO Auto-generated method stub
return null;
}
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
// TODO Auto-generated method stub
return null;
}
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
// TODO Auto-generated method stub
return null;
}
public <T> T unwrap(Class<T> iface) throws SQLException {
// TODO Auto-generated method stub
return null;
}
public boolean isWrapperFor(Class<?> iface) throws SQLException {
// TODO Auto-generated method stub
return false;
}
}
/*--------------------------------------------+
| inner class PoolCleaner |
+--------------------------------------------*/
class PoolCleaner extends Thread {
protected long _cleaningInterval;
protected boolean _mustStop;
public PoolCleaner(long cleaningInterval) {
if (cleaningInterval < 0) {
throw new IllegalArgumentException("cleaningInterval must be >= 0");
}
_mustStop = false;
_cleaningInterval = cleaningInterval;
setDaemon(true);
}
public void run() {
while (!_mustStop) {
try {
sleep(_cleaningInterval);
}
catch (InterruptedException ignore) {
}
if (_mustStop) {
break;
}
removeExpired();
}
}
public void halt() {
_mustStop = true;
synchronized (this) {
this.interrupt();
}
}
}
}