Java/Database SQL JDBC/Connection Pool

Материал из Java эксперт
Перейти к: навигация, поиск

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();
      }
    }
  }
}