All Downloads are FREE. Search and download functionalities are using the official Maven repository.

panda.dao.sql.dbcp.SimpleDataSource Maven / Gradle / Ivy

package panda.dao.sql.dbcp;

import java.io.Closeable;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import panda.dao.sql.Sqls;
import panda.lang.Strings;
import panda.lang.Threads;
import panda.log.Log;
import panda.log.Logs;


/**
 * This is a simple, synchronous, thread-safe database connection pool.
 * 

* * ---- REQUIRED PROPERTIES ---- *

    *
  • jdbc.driver
  • *
  • jdbc.url
  • *
  • jdbc.username
  • *
  • jdbc.password
  • *
  • jdbc.autoCommit - default: false
  • *
  • jdbc.readOnly - default: false
  • *
*

* * ---- POOLING PROPERTIES ---- *

    *
  • pool.maxActive - default: 100
  • *
  • pool.maxIdle - default: 20
  • *
  • pool.maxWait - default: 1000
  • *
  • pool.maxWaitMillis - default: 20000 (milliseconds)
  • *
  • pool.maxCheckoutMillis - default: 1 week (milliseconds)
  • *
  • pool.pingQuery
  • *
  • pool.pingTimeout - default: 1 (seconds)
  • *
  • pool.pingOlderThan - default: 0 (milliseconds)
  • *
  • pool.pingNotUsedFor - default: 600000 (milliseconds)
  • *
*

* */ public class SimpleDataSource extends AbstractDataSource implements Closeable { private static Log log = Logs.getLog(SimpleDataSource.class); //-------------------------------------------------------------------------------------- // PROPERTY FIELDS FOR POOL CONFIGURATION //-------------------------------------------------------------------------------------- private PoolConfig pool = new PoolConfig(); //-------------------------------------------------------------------------------------- // FIELDS LOCKED BY POOL_LOCK //-------------------------------------------------------------------------------------- private final Object POOL_LOCK = new Object(); private List idles = new ArrayList(); private List actives = new ArrayList(); private long requestCount; private long accumulatedRequestTime; private long accumulatedCheckoutTime; private long claimedOverdueConnectionCount; private long accumulatedCheckoutTimeOfOverdueConnections; private long accumulatedWaitTime; private long waiting; private long hadToWaitCount; private long badConnectionCount; /** * Constructor */ public SimpleDataSource() { super(); } /** * @return the pool */ public PoolConfig getPool() { return pool; } /** * @param pool the pool to set */ public void setPool(PoolConfig pool) { this.pool = pool; } /** * Getter for the number of connection requests made * * @return The number of connection requests made */ public long getRequestCount() { return requestCount; } /** * Getter for the average time required to get a connection to the database * * @return The average time */ public long getAverageRequestTime() { return requestCount == 0 ? 0 : accumulatedRequestTime / requestCount; } /** * Getter for the average time spent waiting for connections that were in use * * @return The average time */ public long getAverageWaitTime() { return hadToWaitCount == 0 ? 0 : accumulatedWaitTime / hadToWaitCount; } /** * Getter for the number of requests that had to wait for connections that were in use * * @return The number of requests that had to wait */ public long getHadToWaitCount() { return hadToWaitCount; } /** * Getter for the number of invalid connections that were found in the pool * * @return The number of invalid connections */ public long getBadConnectionCount() { return badConnectionCount; } /** * Getter for the number of connections that were claimed before they were returned * * @return The number of connections */ public long getClaimedOverdueConnectionCount() { return claimedOverdueConnectionCount; } /** * Getter for the average age of overdue connections * * @return The average age */ public long getAverageOverdueCheckoutTime() { return claimedOverdueConnectionCount == 0 ? 0 : accumulatedCheckoutTimeOfOverdueConnections / claimedOverdueConnectionCount; } /** * Getter for the average age of a connection checkout * * @return The average age */ public long getAverageCheckoutTime() { return requestCount == 0 ? 0 : accumulatedCheckoutTime / requestCount; } /** * @return The count of active connections */ public int getActives() { return actives.size(); } /** * Returns the status of the connection pool * * @return The status */ @Override public String getStatus() { StringBuilder sb = new StringBuilder(); sb.append(super.getStatus()); sb.append("\n pool.maxActive ").append(pool.getMaxActive()); sb.append("\n pool.maxIdle ").append(pool.getMaxIdle()); sb.append("\n pool.maxWait ").append(pool.getMaxWait()); sb.append("\n pool.maxWaitMillis ").append(pool.getMaxWaitMillis()); sb.append("\n pool.maxCheckoutMillis ").append(pool.getMaxCheckoutMillis()); sb.append("\n pool.pingQuery ").append(pool.getPingQuery()); sb.append("\n pool.pingTimeout ").append(pool.getPingTimeout()); sb.append("\n pool.pingOlderThan ").append(pool.getPingOlderThan()); sb.append("\n pool.pingNotUsedFor ").append(pool.getPingNotUsedFor()); sb.append("\n --------------------------------------------------------------"); sb.append("\n activeConnections ").append(actives.size()); sb.append("\n idleConnections ").append(idles.size()); sb.append("\n requestCount ").append(getRequestCount()); sb.append("\n averageRequestTime ").append(getAverageRequestTime()); sb.append("\n averageCheckoutTime ").append(getAverageCheckoutTime()); sb.append("\n claimedOverdue ").append(getClaimedOverdueConnectionCount()); sb.append("\n averageOverdueCheckoutTime ").append(getAverageOverdueCheckoutTime()); sb.append("\n hadToWait ").append(getHadToWaitCount()); sb.append("\n averageWaitTime ").append(getAverageWaitTime()); sb.append("\n badConnectionCount ").append(getBadConnectionCount()); sb.append("\n==============================================================="); return sb.toString(); } /** * close connection and remove from pool * @param pcons connection list */ private void close(List pcons) { for (int i = pcons.size() - 1; i >= 0; i--) { try { SimplePooledConnection pcon = pcons.remove(i); pcon.invalidate(); Connection rcon = pcon.getRealConnection(); try { if (!rcon.getAutoCommit() && !rcon.isReadOnly()) { rcon.rollback(); } } catch (SQLException e) { // ignore } finally { Sqls.safeClose(rcon); } } catch (Exception e) { // ignore } } } /** * Closes all of the connections in the pool */ @Override public void close() { synchronized (POOL_LOCK) { if (actives.size() > 0 || idles.size() > 0) { close(actives); close(idles); if (log.isDebugEnabled()) { log.debug("SimpleDataSource forcefully closed/removed all connections."); } } } } protected void pushConnection(SimplePooledConnection pcon) throws SQLException { if (log.isDebugEnabled()) { log.debug("Return connection (" + pcon.getRealHashCode() + ") to the pool."); } synchronized (POOL_LOCK) { actives.remove(pcon); if (!pcon.isValid()) { if (log.isDebugEnabled()) { log.debug("A bad connection (" + pcon.getRealHashCode() + ") attempted to return to the pool, discarding connection."); } badConnectionCount++; return; } accumulatedCheckoutTime += pcon.getCheckoutTime(); boolean valid = true; Connection rcon = pcon.getRealConnection(); try { if (!rcon.getAutoCommit() && !rcon.isReadOnly()) { rcon.rollback(); } } catch (SQLException e) { if (log.isDebugEnabled()) { log.debug("Failed to rollback returned connection " + pcon.getRealHashCode() + ": " + e.getMessage()); } valid = false; } if (valid && (waiting > 0 || idles.size() < pool.maxIdle)) { SimplePooledConnection ncon = new SimplePooledConnection(rcon, this); ncon.setCreatedTimestamp(pcon.getCreatedTimestamp()); ncon.setLastUsedTimestamp(pcon.getLastUsedTimestamp()); idles.add(ncon); pcon.invalidate(); if (log.isDebugEnabled()) { log.debug("Returned connection " + ncon.getRealHashCode() + " to pool."); } POOL_LOCK.notifyAll(); } else { Sqls.safeClose(rcon); pcon.invalidate(); if (log.isDebugEnabled()) { log.debug("Closed connection " + pcon.getRealHashCode() + "."); } } } } @Override protected Connection popConnection() throws SQLException { long start = System.currentTimeMillis(); long waitMillis = pool.maxWaitMillis; synchronized (POOL_LOCK) { if (waiting >= pool.maxWait) { throw new SQLException("Failed to get a connection due to too many wait (" + waiting + ")."); } while (true) { // get a idle connection SimplePooledConnection pcon = getIdleConnection(); if (pcon != null) { checkoutConnection(pcon, start); return pcon; } // get a overdue connection pcon = getOverdueConnection(); if (pcon != null) { checkoutConnection(pcon, start); return pcon; } if (actives.size() < pool.maxActive) { // create a new connection pcon = createConnection(); checkoutConnection(pcon, start); return pcon; } // waited and timeout?? if (waitMillis <= 0) { if (log.isDebugEnabled()) { log.debug("SimpleDataSource: Failed to get a connection due to wait timeout."); } throw new SQLException("Failed to get a connection due to wait timeout."); } // Must wait if (log.isDebugEnabled()) { log.debug("Waiting as long as " + waitMillis + " milliseconds for connection."); } hadToWaitCount++; waiting++; long waitStart = System.currentTimeMillis(); Threads.safeWait(POOL_LOCK, waitMillis); long elapsed = System.currentTimeMillis() - waitStart; waiting--; accumulatedWaitTime += elapsed; waitMillis -= elapsed; } } } private SimplePooledConnection createConnection() throws SQLException { SimplePooledConnection pcon = new SimplePooledConnection(DriverManager.getConnection(jdbc.url, jdbc.prop), this); if (log.isDebugEnabled()) { log.debug("Created connection " + pcon.getRealHashCode() + "."); } return pcon; } private SimplePooledConnection getIdleConnection() { int bad = 0; while (idles.size() > 0) { // Pool has available connection SimplePooledConnection pcon = idles.remove(0); if (log.isDebugEnabled()) { log.debug("Checked out connection " + pcon.getRealHashCode() + " from pool."); } if (pcon.testValid()) { return pcon; } bad++; badConnectionCount++; if (log.isDebugEnabled()) { log.debug("A bad idle connection (" + pcon.getRealHashCode() + ") was returned from the pool, getting another connection " + bad); } continue; } return null; } private SimplePooledConnection getOverdueConnection() { int bad = 0; while (actives.size() > 0) { SimplePooledConnection oldest = (SimplePooledConnection)actives.get(0); long longestCheckoutTime = oldest.getCheckoutTime(); if (longestCheckoutTime > pool.maxCheckoutMillis) { // Can claim overdue connection claimedOverdueConnectionCount++; accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime; accumulatedCheckoutTime += longestCheckoutTime; actives.remove(oldest); SimplePooledConnection pcon = new SimplePooledConnection(oldest.getRealConnection(), this); oldest.invalidate(); if (log.isDebugEnabled()) { log.debug("Claimed overdue connection " + pcon.getRealHashCode() + "."); } if (pcon.testValid()) { return pcon; } bad++; badConnectionCount++; if (log.isDebugEnabled()) { log.debug("A bad overdue connection (" + pcon.getRealHashCode() + ") was returned from the pool, getting another connection " + bad); } continue; } break; } return null; } private void checkoutConnection(SimplePooledConnection pcon, long start) throws SQLException { if (pcon.isReadOnly() != jdbc.readOnly) { pcon.setReadOnly(jdbc.readOnly); } if (pcon.getAutoCommit() != jdbc.autoCommit) { pcon.setAutoCommit(jdbc.autoCommit); } pcon.setCheckoutTimestamp(System.currentTimeMillis()); pcon.setLastUsedTimestamp(System.currentTimeMillis()); actives.add(pcon); requestCount++; accumulatedRequestTime += System.currentTimeMillis() - start; } /** * Method to check to see if a connection is still usable * * @param pcon - the connection to check * @return True if the connection is still usable */ protected boolean pingConnection(SimplePooledConnection pcon) { Connection rcon = pcon.getRealConnection(); try { if (rcon.isClosed()) { return false; } } catch (Exception e) { if (log.isDebugEnabled()) { log.debug("Connection " + pcon.getRealHashCode() + " is BAD: " + e.getMessage()); } return false; } if (Strings.isEmpty(pool.pingQuery) || ((pool.pingOlderThan <= 0 || pcon.getAge() <= pool.pingOlderThan) && (pool.pingNotUsedFor <= 0 || pcon.getTimeElapsedSinceLastUse() <= pool.pingNotUsedFor))) { return true; } if (log.isDebugEnabled()) { log.debug("Testing connection " + pcon.getRealHashCode() + " ..."); } try { Statement statement = rcon.createStatement(); statement.setQueryTimeout(pool.pingTimeout); ResultSet rs = statement.executeQuery(pool.pingQuery); rs.close(); statement.close(); if (log.isDebugEnabled()) { log.debug("Connection " + pcon.getRealHashCode() + " is GOOD!"); } return true; } catch (Exception e) { if (log.isWarnEnabled()) { log.warn("Execution of ping query '" + pool.pingQuery + "' failed: " + e.getMessage()); } Sqls.safeClose(rcon); if (log.isDebugEnabled()) { log.debug("Connection " + pcon.getRealHashCode() + " is BAD: " + e.getMessage()); } return false; } } @Override protected void finalize() throws Throwable { close(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy