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

com.hfg.sql.jdbc.JDBCConnectionPool Maven / Gradle / Ivy

There is a newer version: 20240423
Show newest version
package com.hfg.sql.jdbc;

import com.hfg.datetime.DateUtil;
import com.hfg.security.LoginCredentials;
import com.hfg.util.collection.CollectionUtil;
import com.hfg.util.StringBuilderPlus;
import com.hfg.util.StringUtil;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

//------------------------------------------------------------------------------
/**
 A JDBC-compatible connection pool.
 
@author J. Alex Taylor, hairyfatguy.com
*/ //------------------------------------------------------------------------------ // com.hfg XML/HTML Coding Library // // 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com // [email protected] //------------------------------------------------------------------------------ public abstract class JDBCConnectionPool extends JDBCDataSource { private List mPooledConnections; private Thread mIdleConnReaperThread; private int mNumConnectionRequests = 0; private boolean mIsActive = true; private Set mBorrowerClassesToIgnore; //########################################################################### // CONSTRUCTORS //########################################################################### //--------------------------------------------------------------------------- public JDBCConnectionPool(JDBCServer inServer, String inDatabaseName) { super(inServer, inDatabaseName); } //--------------------------------------------------------------------------- public JDBCConnectionPool(JDBCServer inServer, String inDatabaseName, LoginCredentials inCredentials) { super(inServer, inDatabaseName); setCredentials(inCredentials); } //########################################################################### // PUBLIC METHODS //########################################################################### //--------------------------------------------------------------------------- public JDBCConnection getConnection() throws SQLException, NoAvailableConnectionException { JDBCConnection conn = null; long startTime = System.currentTimeMillis(); StackTraceElement borrower = getBorrower(); while (null == conn) { try { conn = getPoolConnection(borrower); } catch (NoAvailableConnectionException e) { if (getSettings().getAvailabilityTimeoutSec() != null && System.currentTimeMillis() - startTime > (getSettings().getAvailabilityTimeoutSec() * 1000)) { throw e; } else { try { Thread.sleep(250); } catch (InterruptedException e2) { // Ignore } } } } mNumConnectionRequests++; return conn; } //--------------------------------------------------------------------------- public synchronized void destroy() throws SQLException { mIsActive = false; if (CollectionUtil.hasValues(mPooledConnections)) { for (PooledJDBCConnection conn : mPooledConnections) { conn.destroy(); } mPooledConnections.clear(); } mIdleConnReaperThread.interrupt(); } //--------------------------------------------------------------------------- /** Specifies a class to be ignored when tracking connection lending. When examining the stacktrace to see who is requesting a connection, any elements from this class will be skipped and a subsequent element will be used to identify the borrower. * @param inValue a class to be ignored when tracking connection lending * @return this connection pool object to enable method chaining */ public JDBCConnectionPool addIgnorableBorrowerClass(Class inValue) { if (inValue != null) { if (null == mBorrowerClassesToIgnore) { mBorrowerClassesToIgnore = new HashSet<>(5); } mBorrowerClassesToIgnore.add(inValue.getName()); } return this; } //--------------------------------------------------------------------------- public String getStatusReport() { StringBuilderPlus buffer = new StringBuilderPlus(); buffer.appendln("Connection Pool Status Report " + DateUtil.getYYYY_MM_DD_HH_mm_ss(new Date())); buffer.appendln(); buffer.appendln(" Server: " + getServer().getConnectString(getDatabaseName())); buffer.appendln(" Num Connection Requests: " + mNumConnectionRequests); buffer.appendln(String.format(" Connections (Min/Max/Avail.): %d / %d/ %d", getSettings().getMinConnections(), getSettings().getMaxConnections(), getNumAvailableConnections())); buffer.appendln(" Active Connections:"); for (int i = 0; i < mPooledConnections.size(); i++) { PooledJDBCConnection poolConn = mPooledConnections.get(i); if (! poolConn.isAvailable()) { buffer.appendln(String.format(" %2d. %s [%s]", (i+1), poolConn.getBorrower().toString(), DateUtil.generateElapsedTimeString(poolConn.getmCheckoutTime()))); } } return buffer.toString(); } //########################################################################### // PROTECTED METHODS //########################################################################### //--------------------------------------------------------------------------- protected synchronized JDBCConnection getPoolConnection(StackTraceElement inBorrower) throws SQLException, NoAvailableConnectionException { PooledJDBCConnection conn = null; if (null == mPooledConnections) { initializePool(); } for (int i = 0; i < mPooledConnections.size(); i++) { PooledJDBCConnection poolConn = mPooledConnections.get(i); if (poolConn.isAvailable()) { try { conn = poolConn.checkout(inBorrower); if (null == conn) { // The connection must not be valid anymore poolConn.destroy(); mPooledConnections.remove(i--); } else { // Connection is good to go break; } } catch (Exception e) { e.printStackTrace(); poolConn.destroy(); mPooledConnections.remove(i--); } } } if (null == conn) { // No available connections were found in the pool. Can we create a new one? if (mPooledConnections.size() < getSettings().getMaxConnections()) { conn = new PooledJDBCConnection(createConnection()); mPooledConnections.add(conn); } else { throw new NoAvailableConnectionException(); } } return conn; } //--------------------------------------------------------------------------- protected T initSettings() { return (T) new JDBCConnectionPoolSettings(); } //--------------------------------------------------------------------------- protected abstract JDBCConnection createConnection(); //########################################################################### // PRIVATE METHODS //########################################################################### //--------------------------------------------------------------------------- private void initializePool() { mPooledConnections = new ArrayList<>(getSettings().getMaxConnections()); for (int i = 0; i < getSettings().getMinConnections(); i++) { JDBCConnection conn = createConnection(); mPooledConnections.add(new PooledJDBCConnection(conn)); } mIdleConnReaperThread = new Thread(new IdleConnReaper()); mIdleConnReaperThread.setDaemon(true); mIdleConnReaperThread.setPriority(Thread.MIN_PRIORITY); mIdleConnReaperThread.setName(generateIdleRepearThreadName()); mIdleConnReaperThread.start(); } //--------------------------------------------------------------------------- private String generateIdleRepearThreadName() { StringBuilderPlus name = new StringBuilderPlus().setDelimiter(" "); if (StringUtil.isSet(name())) { name.append(name()); } if (StringUtil.isSet(getServer().name())) { name.delimitedAppend(getServer().name()); } if (StringUtil.isSet(getDatabaseName())) { name.delimitedAppend(getDatabaseName()); } name.delimitedAppend(" Idle Conn Reaper"); return name.toString(); } //--------------------------------------------------------------------------- private StackTraceElement getBorrower() { StackTraceElement borrower = null; StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); for (int i = 3; i < stackTrace.length; i++) { StackTraceElement stackTraceElement = stackTrace[i]; if (! CollectionUtil.hasValues(mBorrowerClassesToIgnore) || ! mBorrowerClassesToIgnore.contains(stackTraceElement.getClassName())) { borrower = stackTraceElement; break; } } return borrower; } //--------------------------------------------------------------------------- private synchronized void removeConnectionFromPool(PooledJDBCConnection inConn) { mPooledConnections.remove(inConn); } //--------------------------------------------------------------------------- private synchronized void removeIdledConnections() throws SQLException { if (poolContainsMoreThanTheMinNumOfConnections()) { for (int i = 0; i < getSettings().getMinConnections(); i++) { PooledJDBCConnection poolConn = mPooledConnections.get(i); if (poolConn.isAvailable() && poolConn.getIdleTimeSec() > getSettings().getMaxIdleTimeSec()) { poolConn.destroy(); mPooledConnections.remove(i--); if (! poolContainsMoreThanTheMinNumOfConnections()) { break; } } } } } //--------------------------------------------------------------------------- private boolean poolContainsMoreThanTheMinNumOfConnections() { return (mPooledConnections.size() > 0 && (null == getSettings().getMinConnections() || mPooledConnections.size() > getSettings().getMinConnections())); } //--------------------------------------------------------------------------- private int getNumAvailableConnections() { int numAvailableConns = 0; if (mPooledConnections != null) { for (int i = 0; i < mPooledConnections.size(); i++) { PooledJDBCConnection poolConn = mPooledConnections.get(i); if (poolConn.isAvailable()) { numAvailableConns++; } } } return numAvailableConns; } //########################################################################### // INNER CLASS //########################################################################### private class PooledJDBCConnection extends JDBCConnection { private boolean mIsAvailable; private Long mCheckoutTime; private StackTraceElement mBorrower; private Long mIdleStartTime; //------------------------------------------------------------------------ public PooledJDBCConnection(JDBCConnection inConn) { super(inConn); initPooledConnection(); } //------------------------------------------------------------------------ private void initPooledConnection() { mIsAvailable = true; mCheckoutTime = null; mBorrower = null; mIdleStartTime = System.currentTimeMillis(); } //------------------------------------------------------------------------ public boolean isAvailable() { return mIsAvailable; } //------------------------------------------------------------------------ public PooledJDBCConnection checkout(StackTraceElement inBorrower) throws SQLException { mIsAvailable = false; mCheckoutTime = System.currentTimeMillis(); mBorrower = inBorrower; mIdleStartTime = null; setAutoCommit(true); PooledJDBCConnection conn = this; if (getServer().getRDBMS().getConnTestQuery() != null) { try { execute(getServer().getRDBMS().getConnTestQuery().toSQL()); } catch (JDBCException e) { conn = null; } } return conn; } //------------------------------------------------------------------------ public Long getmCheckoutTime() { return mCheckoutTime; } //------------------------------------------------------------------------ public StackTraceElement getBorrower() { return mBorrower; } //------------------------------------------------------------------------ private int getIdleTimeSec() { int idleTimeSec = 0; if (mIdleStartTime != null) { idleTimeSec = (int) (System.currentTimeMillis() - mIdleStartTime)/1000; } return idleTimeSec; } //------------------------------------------------------------------------ // Don't actually close the underlying connection. It goes back into the pool. public void close() throws SQLException { if (isClosed()) { removeConnectionFromPool(this); } else { clean(); initPooledConnection(); } } //------------------------------------------------------------------------ public void destroy() throws SQLException { if (! isClosed()) { unwrap(Connection.class).close(); } } //------------------------------------------------------------------------ private void clean() throws SQLException { if (! getAutoCommit()) { boolean returnedInUncommittedState = false; try { setAutoCommit(true); } catch (SQLException e) { returnedInUncommittedState = true; // Try rolling back the transaction. try { rollback(); setAutoCommit(true); } catch (SQLException e2) { destroy(); removeConnectionFromPool(this); } } if (returnedInUncommittedState) { throw new JDBCException("Connection returned in an uncommitted state!"); } } } } //########################################################################### // INNER CLASS //########################################################################### private class IdleConnReaper implements Runnable { private int mExecutionFreqSec = 60; //------------------------------------------------------------------------ public void run() { while (mIsActive) { try { Thread.sleep(mExecutionFreqSec * 1000); } catch (InterruptedException e2) { // Ignore } try { removeIdledConnections(); } catch (Exception e) { // Ignore e.printStackTrace(); } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy