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

com.avaje.ebeaninternal.server.lib.sql.DataSourcePool Maven / Gradle / Ivy

/**
 *  Copyright (C) 2006  Robin Bygrave
 *  
 *  This library 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 2.1 of the License, or (at your option) any later version.
 *  
 *  This library 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
 *  Lesser General Public License for more details.
 *  
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */
package com.avaje.ebeaninternal.server.lib.sql;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.persistence.PersistenceException;
import javax.sql.DataSource;

import com.avaje.ebean.config.DataSourceConfig;
import com.avaje.ebeaninternal.api.ClassUtil;
import com.avaje.ebeaninternal.server.lib.cron.CronManager;

/**
 * A robust DataSource.
 * 

*

    *
  • Manages the number of connections closing connections that have been idle * for some time. *
  • Notifies when the datasource goes down and comes back up. *
  • Checks for expected downtime which is useful for schedule db backups. *
  • Provides PreparedStatement caching *
  • Knows the busy connections *
  • Traces connections that have been leaked *
*

*/ public class DataSourcePool implements DataSource { private static final Logger logger = Logger.getLogger(DataSourcePool.class.getName()); /** * The name given to this dataSource. */ private final String name; /** * Used to notify of changes to the DataSource status. */ private final DataSourceNotify notify; /** * Optional listener that can be notified when connections are got from and * put back into the pool. */ private final DataSourcePoolListener poolListener; /** * Properties used to create a Connection. */ private final Properties connectionProps; /** * The jdbc connection url. */ private final String databaseUrl; /** * The jdbc driver. */ private final String databaseDriver; /** * The sql used to test a connection. */ private final String heartbeatsql; /** * The transaction isolation level as per java.sql.Connection. */ private final int transactionIsolation; /** * The default autoCommit setting for Connections in this pool. */ private final boolean autoCommit; /** * Flag set to true to capture stackTraces (can be expensive). */ private boolean captureStackTrace; /** * The max size of the stack trace to report. */ private int maxStackTraceSize; /** * flag to indicate we have sent an alert message. */ private boolean dataSourceDownAlertSent; /** * The time the pool was last trimmed. */ private long lastTrimTime; /** * Assume that the DataSource is up. heartBeat checking will discover when * it goes down, and comes back up again. */ private boolean dataSourceUp = true; /** * The current alert. */ private boolean inWarningMode; /** * The minimum number of connections this pool will maintain. */ private int minConnections; /** * The maximum number of connections this pool will grow to. */ private int maxConnections; /** * The number of connections to exceed before a warning Alert is fired. */ private int warningSize; /** * The time a thread will wait for a connection to become available. */ private int waitTimeoutMillis; /** * The size of the preparedStatement cache; */ private int pstmtCacheSize; /** * By default trim connections that are inactive for longer than this time. */ private int maxInactiveTimeSecs; private final PooledConnectionQueue queue; /** * Used to find and close() leaked connections. Leaked connections are * thought to be busy but have not been used for some time. Each time a * connection is used it sets it's lastUsedTime. */ private long leakTimeMinutes; public DataSourcePool(DataSourceNotify notify, String name, DataSourceConfig params) { this.notify = notify; this.name = name; this.poolListener = createPoolListener(params.getPoolListener()); this.autoCommit = false; this.transactionIsolation = Connection.TRANSACTION_READ_COMMITTED; this.maxInactiveTimeSecs = params.getMaxInactiveTimeSecs(); this.leakTimeMinutes = params.getLeakTimeMinutes(); this.captureStackTrace = params.isCaptureStackTrace(); this.maxStackTraceSize = params.getMaxStackTraceSize(); this.databaseDriver = params.getDriver(); this.databaseUrl = params.getUrl(); this.pstmtCacheSize = params.getPstmtCacheSize(); this.minConnections = params.getMinConnections(); this.maxConnections = params.getMaxConnections(); this.waitTimeoutMillis = params.getWaitTimeoutMillis(); this.heartbeatsql = params.getHeartbeatSql(); queue = new PooledConnectionQueue(this); String un = params.getUsername(); String pw = params.getPassword(); if (un == null) { throw new RuntimeException("DataSource user is null?"); } if (pw == null) { throw new RuntimeException("DataSource password is null?"); } this.connectionProps = new Properties(); this.connectionProps.setProperty("user", un); this.connectionProps.setProperty("password", pw); try { initialise(); } catch (SQLException ex) { throw new DataSourceException(ex); } } /** * Create the DataSourcePoolListener if there is one. */ private DataSourcePoolListener createPoolListener(String cn) { if (cn == null) { return null; } try { return (DataSourcePoolListener)ClassUtil.newInstance(cn, this.getClass()); } catch (Exception e) { throw new DataSourceException(e); } } private void initialise() throws SQLException { // Ensure database driver is loaded try { ClassUtil.forName(this.databaseDriver, this.getClass()); } catch (Throwable e) { throw new PersistenceException("Problem loading Database Driver [" + this.databaseDriver + "]: " + e.getMessage(), e); } String transIsolation = TransactionIsolation.getLevelDescription(transactionIsolation); StringBuffer sb = new StringBuffer(); sb.append("DataSourcePool [").append(name); sb.append("] autoCommit[").append(autoCommit); sb.append("] transIsolation[").append(transIsolation); sb.append("] min[").append(minConnections); sb.append("] max[").append(maxConnections).append("]"); logger.info(sb.toString()); queue.ensureMinimumConnections(); } /** * Returns false. */ public boolean isWrapperFor(Class arg0) throws SQLException { return false; } /** * Not Implemented. */ public T unwrap(Class arg0) throws SQLException { throw new SQLException("Not Implemented"); } /** * Return the dataSource name. */ public String getName() { return name; } /** * Return the max size of stack traces used when trying to find connection pool leaks. *

* This is only used when {@link #isCaptureStackTrace()} is true. *

*/ public int getMaxStackTraceSize() { return maxStackTraceSize; } /** * Returns false when the dataSource is down. */ public boolean isDataSourceUp() { return dataSourceUp; } /** * Called when the pool hits the warning level. */ protected void notifyWarning(String msg) { if (!inWarningMode) { // send an Error to the event log... inWarningMode = true; logger.warning(msg); if (notify != null) { String subject = "DataSourcePool [" + name + "] warning"; notify.notifyWarning(subject, msg); } } } private void notifyDataSourceIsDown(SQLException ex) { if (isExpectedToBeDownNow()) { if (dataSourceUp) { String msg = "DataSourcePool [" + name + "] is down but in downtime!"; logger.log(Level.WARNING, msg, ex); } } else if (!dataSourceDownAlertSent) { String msg = "FATAL: DataSourcePool [" + name + "] is down!!!"; logger.log(Level.SEVERE, msg, ex); if (notify != null) { notify.notifyDataSourceDown(name); } dataSourceDownAlertSent = true; } if (dataSourceUp) { reset(); } dataSourceUp = false; } private void notifyDataSourceIsUp() { if (dataSourceDownAlertSent) { String msg = "RESOLVED FATAL: DataSourcePool [" + name + "] is back up!"; logger.log(Level.SEVERE, msg); if (notify != null) { notify.notifyDataSourceUp(name); } dataSourceDownAlertSent = false; } else if (!dataSourceUp) { logger.log(Level.WARNING, "DataSourcePool [" + name + "] is back up!"); } if (!dataSourceUp) { dataSourceUp = true; reset(); } } /** * Check the dataSource is up. Trim connections. */ protected void checkDataSource() { Connection conn = null; try { // test to see if we can create a new connection... conn = getConnection(); testConnection(conn); notifyDataSourceIsUp(); if (System.currentTimeMillis() > (lastTrimTime + (maxInactiveTimeSecs * 1000))) { queue.trim(maxInactiveTimeSecs); lastTrimTime = System.currentTimeMillis(); } } catch (SQLException ex) { notifyDataSourceIsDown(ex); } finally { try { if (conn != null) { conn.close(); } } catch (SQLException ex) { logger.log(Level.WARNING, "Can't close connection in checkDataSource!"); } } } private boolean isExpectedToBeDownNow() { // downtime as controlled as a scheduled task // for example, if the database is down as 23.50 every night // for 10 minutes schedule the ...lib.cron.Downtime to run // at that time for that duration. return CronManager.isDowntime(); } /** * Create a Connection that will not be part of the connection pool. * *

* When this connection is closed it will not go back into the pool. *

* *

* If withDefaults is true then the Connection will have the autoCommit and * transaction isolation set to the defaults for the pool. *

*/ public Connection createUnpooledConnection() throws SQLException { try { Connection conn = DriverManager.getConnection(databaseUrl, connectionProps); conn.setAutoCommit(autoCommit); conn.setTransactionIsolation(transactionIsolation); return conn; } catch (SQLException ex) { notifyDataSourceIsDown(null); throw ex; } } /** * Set a new maximum size. The pool should respect this new maximum * immediately and not require a restart. You may want to increase the * maxConnections if the pool gets large and hits the warning level. */ public void setMaxSize(int max) { queue.setMaxSize(max); this.maxConnections = max; } /** * Return the max size this pool can grow to. */ public int getMaxSize() { return maxConnections; } /** * Set the min size this pool should maintain. */ public void setMinSize(int min) { queue.setMinSize(min); this.minConnections = min; } /** * Return the min size this pool should maintain. */ public int getMinSize() { return minConnections; } /** * Set a new maximum size. The pool should respect this new maximum * immediately and not require a restart. You may want to increase the * maxConnections if the pool gets large and hits the warning and or alert * levels. */ public void setWarningSize(int warningSize) { queue.setWarningSize(warningSize); this.warningSize = warningSize; } /** * Return the warning size. When the pool hits this size it can send a * notify message to an administrator. */ public int getWarningSize() { return warningSize; } /** * Return the time in millis that threads will wait when the pool has hit * the max size. These threads wait for connections to be returned by the * busy connections. */ public int getWaitTimeoutMillis() { return waitTimeoutMillis; } /** * Set the time after which inactive connections are trimmed. */ public void setMaxInactiveTimeSecs(int maxInactiveTimeSecs) { this.maxInactiveTimeSecs = maxInactiveTimeSecs; } /** * Return the time after which inactive connections are trimmed. */ public int getMaxInactiveTimeSecs() { return maxInactiveTimeSecs; } private void testConnection(Connection conn) throws SQLException { if (heartbeatsql == null) { return; } Statement stmt = null; ResultSet rset = null; try { // It should only error IF the DataSource is down ? (or a network // issue?) stmt = conn.createStatement(); rset = stmt.executeQuery(heartbeatsql); conn.commit(); } finally { try { if (rset != null) { rset.close(); } } catch (SQLException e) { logger.log(Level.SEVERE, null, e); } try { if (stmt != null) { stmt.close(); } } catch (SQLException e) { logger.log(Level.SEVERE, null, e); } } } /** * Make sure the connection is still ok to use. If not then remove it from * the pool. */ protected boolean validateConnection(PooledConnection conn) { try { if (heartbeatsql == null) { logger.info("Can not test connection as heartbeatsql is not set"); return false; } testConnection(conn); return true; } catch (Exception e) { String desc = "heartbeatsql test failed on connection[" + conn.getName() + "]"; logger.warning(desc); return false; } } /** * Called by the PooledConnection themselves, returning themselves to the * pool when they have been finished with. *

* Note that connections may not be added back to the pool if returnToPool * is false or if they where created before the recycleTime. In both of * these cases the connection is fully closed and not pooled. *

* * @param pooledConnection * the returning connection * */ protected void returnConnection(PooledConnection pooledConnection) { if (poolListener != null) { poolListener.onBeforeReturnConnection(pooledConnection); } queue.returnPooledConnection(pooledConnection); } /** * Returns information describing connections that are currently being used. */ public String getBusyConnectionInformation() { return queue.getBusyConnectionInformation(); } /** * Dumps the busy connection information to the logs. *

* This includes the stackTrace elements if they are being captured. This is * useful when needing to look a potential connection pool leaks. *

*/ public void dumpBusyConnectionInformation() { queue.dumpBusyConnectionInformation(); } /** * Close any busy connections that have not been used for some time. *

* These connections are considered to have leaked from the connection pool. *

*

* Connection leaks occur when code doesn't ensure that connections are * closed() after they have been finished with. There should be an * appropriate try catch finally block to ensure connections are always * closed and put back into the pool. *

*/ public void closeBusyConnections(long leakTimeMinutes) { queue.closeBusyConnections(leakTimeMinutes); } /** * Grow the pool by creating a new connection. The connection can either be * added to the available list, or returned. *

* This method is protected by synchronisation in calling methods. *

*/ protected PooledConnection createConnectionForQueue(int connId) throws SQLException { try { Connection c = createUnpooledConnection(); PooledConnection pc = new PooledConnection(this, connId, c); pc.resetForUse(); if (!dataSourceUp) { notifyDataSourceIsUp(); } return pc; } catch (SQLException ex) { notifyDataSourceIsDown(ex); throw ex; } } /** * Close all the connections in the pool. *

*

    *
  • Checks that the database is up. *
  • Resets the Alert level. *
  • Closes busy connections that have not been used for some time (aka * leaks). *
  • This closes all the currently available connections. *
  • Busy connections are closed when they are returned to the pool. *
*

*/ public void reset() { queue.reset(leakTimeMinutes); inWarningMode = false; } /** * Return a pooled connection. */ public Connection getConnection() throws SQLException { return getPooledConnection(); } /** * Get a connection from the pool. *

* This will grow the pool if all the current connections are busy. This * will go into a wait if the pool has hit its maximum size. *

*/ public PooledConnection getPooledConnection() throws SQLException { PooledConnection c = queue.getPooledConnection(); if (captureStackTrace) { c.setStackTrace(Thread.currentThread().getStackTrace()); } if (poolListener != null) { poolListener.onAfterBorrowConnection(c); } return c; } /** * Send a message to the DataSourceAlertListener to test it. This is so that * you can make sure the alerter is configured correctly etc. */ public void testAlert() { String subject = "Test DataSourcePool [" + name + "]"; String msg = "Just testing if alert message is sent successfully."; if (notify != null) { notify.notifyWarning(subject, msg); } } /** * This will close all the free connections, and then go into a wait loop, * waiting for the busy connections to be freed. * *

* The DataSources's should be shutdown AFTER thread pools. Leaked * Connections are not waited on, as that would hang the server. *

*/ public void shutdown() { queue.shutdown(); } /** * Return the default autoCommit setting Connections in this pool will use. * * @return true if the pool defaults autoCommit to true */ public boolean getAutoCommit() { return autoCommit; } /** * Return the default transaction isolation level connections in this pool * should have. * * @return the default transaction isolation level */ public int getTransactionIsolation() { return transactionIsolation; } /** * Return true if the connection pool is currently capturing the StackTrace * when connections are 'got' from the pool. *

* This is set to true to help diagnose connection pool leaks. *

*/ public boolean isCaptureStackTrace() { return captureStackTrace; } /** * Set this to true means that the StackElements are captured every time a * connection is retrieved from the pool. This can be used to identify * connection pool leaks. */ public void setCaptureStackTrace(boolean captureStackTrace) { this.captureStackTrace = captureStackTrace; } /** * Not implemented and shouldn't be used. */ public Connection getConnection(String username, String password) throws SQLException { throw new SQLException("Method not supported"); } /** * Not implemented and shouldn't be used. */ public int getLoginTimeout() throws SQLException { throw new SQLException("Method not supported"); } /** * Not implemented and shouldn't be used. */ public void setLoginTimeout(int seconds) throws SQLException { throw new SQLException("Method not supported"); } /** * Returns null. */ public PrintWriter getLogWriter() { return null; } /** * Not implemented. */ public void setLogWriter(PrintWriter writer) throws SQLException { throw new SQLException("Method not supported"); } /** * For detecting and closing leaked connections. Connections that have been * busy for more than leakTimeMinutes are considered leaks and will be * closed on a reset(). *

* If you want to use a connection for that longer then you should consider * creating an unpooled connection or setting longRunning to true on that * connection. *

*/ public void setLeakTimeMinutes(long leakTimeMinutes) { this.leakTimeMinutes = leakTimeMinutes; } /** * Return the number of minutes after which a busy connection could be * considered leaked from the connection pool. */ public long getLeakTimeMinutes() { return leakTimeMinutes; } /** * Return the preparedStatement cache size. */ public int getPstmtCacheSize() { return pstmtCacheSize; } /** * Set the preparedStatement cache size. */ public void setPstmtCacheSize(int pstmtCacheSize) { this.pstmtCacheSize = pstmtCacheSize; } /** * Return the current status of the connection pool. *

* If you pass reset = true then the counters such as * hitCount, waitCount and highWaterMark are reset. *

*/ public Status getStatus(boolean reset) { return queue.getStatus(reset); } public static class Status { private final String name; private final int minSize; private final int maxSize; private final int free; private final int busy; private final int waiting; private final int highWaterMark; private final int waitCount; private final int hitCount; protected Status(String name, int minSize, int maxSize, int free, int busy, int waiting, int highWaterMark, int waitCount, int hitCount) { this.name = name; this.minSize = minSize; this.maxSize = maxSize; this.free = free; this.busy = busy; this.waiting = waiting; this.highWaterMark = highWaterMark; this.waitCount = waitCount; this.hitCount = hitCount; } public String toString() { return "min:" + minSize + " max:" + maxSize + " free:" + free + " busy:" + busy + " waiting:" + waiting + " highWaterMark:" + highWaterMark + " waitCount:" + waitCount + " hitCount:" + hitCount; } /** * Return the DataSource name. */ public String getName() { return name; } /** * Return the min pool size. */ public int getMinSize() { return minSize; } /** * Return the max pool size. */ public int getMaxSize() { return maxSize; } /** * Return the current number of free connections in the pool. */ public int getFree() { return free; } /** * Return the current number of busy connections in the pool. */ public int getBusy() { return busy; } /** * Return the current number of threads waiting for a connection. */ public int getWaiting() { return waiting; } /** * Return the high water mark of busy connections. */ public int getHighWaterMark() { return highWaterMark; } /** * Return the total number of times a thread had to wait. */ public int getWaitCount() { return waitCount; } /** * Return the total number of times there was an attempt to get a * connection. *

* If the attempt to get a connection failed with a timeout or other * exception those attempts are still included in this hit count. *

*/ public int getHitCount() { return hitCount; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy