- /*
- * Copyright 2009-2024 the original author or authors.
- *
- * Licensed 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
- *
- * https://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.
- */
- package org.apache.ibatis.datasource.pooled;
- import java.io.PrintWriter;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Proxy;
- import java.sql.Connection;
- import java.sql.DriverManager;
- import java.sql.SQLException;
- import java.sql.Statement;
- import java.util.Properties;
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.locks.Condition;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- import java.util.logging.Logger;
- import javax.sql.DataSource;
- import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
- import org.apache.ibatis.logging.Log;
- import org.apache.ibatis.logging.LogFactory;
- /**
- * This is a simple, synchronous, thread-safe database connection pool.
- *
- * @author Clinton Begin
- */
- public class PooledDataSource implements DataSource {
- private static final Log log = LogFactory.getLog(PooledDataSource.class);
- private final PoolState state = new PoolState(this);
- private final UnpooledDataSource dataSource;
- // OPTIONAL CONFIGURATION FIELDS
- protected int poolMaximumActiveConnections = 10;
- protected int poolMaximumIdleConnections = 5;
- protected int poolMaximumCheckoutTime = 20000;
- protected int poolTimeToWait = 20000;
- protected int poolMaximumLocalBadConnectionTolerance = 3;
- protected String poolPingQuery = "NO PING QUERY SET";
- protected boolean poolPingEnabled;
- protected int poolPingConnectionsNotUsedFor;
- private int expectedConnectionTypeCode;
- private final Lock lock = new ReentrantLock();
- private final Condition condition = lock.newCondition();
- public PooledDataSource() {
- dataSource = new UnpooledDataSource();
- }
- public PooledDataSource(UnpooledDataSource dataSource) {
- this.dataSource = dataSource;
- }
- public PooledDataSource(String driver, String url, String username, String password) {
- dataSource = new UnpooledDataSource(driver, url, username, password);
- expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(),
- dataSource.getPassword());
- }
- public PooledDataSource(String driver, String url, Properties driverProperties) {
- dataSource = new UnpooledDataSource(driver, url, driverProperties);
- expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(),
- dataSource.getPassword());
- }
- public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, String username, String password) {
- dataSource = new UnpooledDataSource(driverClassLoader, driver, url, username, password);
- expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(),
- dataSource.getPassword());
- }
- public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, Properties driverProperties) {
- dataSource = new UnpooledDataSource(driverClassLoader, driver, url, driverProperties);
- expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(),
- dataSource.getPassword());
- }
- @Override
- public Connection getConnection() throws SQLException {
- return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
- }
- @Override
- public Connection getConnection(String username, String password) throws SQLException {
- return popConnection(username, password).getProxyConnection();
- }
- @Override
- public void setLoginTimeout(int loginTimeout) {
- DriverManager.setLoginTimeout(loginTimeout);
- }
- @Override
- public int getLoginTimeout() {
- return DriverManager.getLoginTimeout();
- }
- @Override
- public void setLogWriter(PrintWriter logWriter) {
- DriverManager.setLogWriter(logWriter);
- }
- @Override
- public PrintWriter getLogWriter() {
- return DriverManager.getLogWriter();
- }
- public void setDriver(String driver) {
- dataSource.setDriver(driver);
- forceCloseAll();
- }
- public void setUrl(String url) {
- dataSource.setUrl(url);
- forceCloseAll();
- }
- public void setUsername(String username) {
- dataSource.setUsername(username);
- forceCloseAll();
- }
- public void setPassword(String password) {
- dataSource.setPassword(password);
- forceCloseAll();
- }
- public void setDefaultAutoCommit(boolean defaultAutoCommit) {
- dataSource.setAutoCommit(defaultAutoCommit);
- forceCloseAll();
- }
- public void setDefaultTransactionIsolationLevel(Integer defaultTransactionIsolationLevel) {
- dataSource.setDefaultTransactionIsolationLevel(defaultTransactionIsolationLevel);
- forceCloseAll();
- }
- public void setDriverProperties(Properties driverProps) {
- dataSource.setDriverProperties(driverProps);
- forceCloseAll();
- }
- /**
- * Sets the default network timeout value to wait for the database operation to complete. See
- * {@link Connection#setNetworkTimeout(java.util.concurrent.Executor, int)}
- *
- * @param milliseconds
- * The time in milliseconds to wait for the database operation to complete.
- *
- * @since 3.5.2
- */
- public void setDefaultNetworkTimeout(Integer milliseconds) {
- dataSource.setDefaultNetworkTimeout(milliseconds);
- forceCloseAll();
- }
- /**
- * The maximum number of active connections.
- *
- * @param poolMaximumActiveConnections
- * The maximum number of active connections
- */
- public void setPoolMaximumActiveConnections(int poolMaximumActiveConnections) {
- this.poolMaximumActiveConnections = poolMaximumActiveConnections;
- forceCloseAll();
- }
- /**
- * The maximum number of idle connections.
- *
- * @param poolMaximumIdleConnections
- * The maximum number of idle connections
- */
- public void setPoolMaximumIdleConnections(int poolMaximumIdleConnections) {
- this.poolMaximumIdleConnections = poolMaximumIdleConnections;
- forceCloseAll();
- }
- /**
- * The maximum number of tolerance for bad connection happens in one thread which are applying for new
- * {@link PooledConnection}.
- *
- * @param poolMaximumLocalBadConnectionTolerance
- * max tolerance for bad connection happens in one thread
- *
- * @since 3.4.5
- */
- public void setPoolMaximumLocalBadConnectionTolerance(int poolMaximumLocalBadConnectionTolerance) {
- this.poolMaximumLocalBadConnectionTolerance = poolMaximumLocalBadConnectionTolerance;
- }
- /**
- * The maximum time a connection can be used before it *may* be given away again.
- *
- * @param poolMaximumCheckoutTime
- * The maximum time
- */
- public void setPoolMaximumCheckoutTime(int poolMaximumCheckoutTime) {
- this.poolMaximumCheckoutTime = poolMaximumCheckoutTime;
- forceCloseAll();
- }
- /**
- * The time to wait before retrying to get a connection.
- *
- * @param poolTimeToWait
- * The time to wait
- */
- public void setPoolTimeToWait(int poolTimeToWait) {
- this.poolTimeToWait = poolTimeToWait;
- forceCloseAll();
- }
- /**
- * The query to be used to check a connection.
- *
- * @param poolPingQuery
- * The query
- */
- public void setPoolPingQuery(String poolPingQuery) {
- this.poolPingQuery = poolPingQuery;
- forceCloseAll();
- }
- /**
- * Determines if the ping query should be used.
- *
- * @param poolPingEnabled
- * True if we need to check a connection before using it
- */
- public void setPoolPingEnabled(boolean poolPingEnabled) {
- this.poolPingEnabled = poolPingEnabled;
- forceCloseAll();
- }
- /**
- * If a connection has not been used in this many milliseconds, ping the database to make sure the connection is still
- * good.
- *
- * @param milliseconds
- * the number of milliseconds of inactivity that will trigger a ping
- */
- public void setPoolPingConnectionsNotUsedFor(int milliseconds) {
- this.poolPingConnectionsNotUsedFor = milliseconds;
- forceCloseAll();
- }
- public String getDriver() {
- return dataSource.getDriver();
- }
- public String getUrl() {
- return dataSource.getUrl();
- }
- public String getUsername() {
- return dataSource.getUsername();
- }
- public String getPassword() {
- return dataSource.getPassword();
- }
- public boolean isAutoCommit() {
- return dataSource.isAutoCommit();
- }
- public Integer getDefaultTransactionIsolationLevel() {
- return dataSource.getDefaultTransactionIsolationLevel();
- }
- public Properties getDriverProperties() {
- return dataSource.getDriverProperties();
- }
- /**
- * Gets the default network timeout.
- *
- * @return the default network timeout
- *
- * @since 3.5.2
- */
- public Integer getDefaultNetworkTimeout() {
- return dataSource.getDefaultNetworkTimeout();
- }
- public int getPoolMaximumActiveConnections() {
- return poolMaximumActiveConnections;
- }
- public int getPoolMaximumIdleConnections() {
- return poolMaximumIdleConnections;
- }
- public int getPoolMaximumLocalBadConnectionTolerance() {
- return poolMaximumLocalBadConnectionTolerance;
- }
- public int getPoolMaximumCheckoutTime() {
- return poolMaximumCheckoutTime;
- }
- public int getPoolTimeToWait() {
- return poolTimeToWait;
- }
- public String getPoolPingQuery() {
- return poolPingQuery;
- }
- public boolean isPoolPingEnabled() {
- return poolPingEnabled;
- }
- public int getPoolPingConnectionsNotUsedFor() {
- return poolPingConnectionsNotUsedFor;
- }
- /**
- * Closes all active and idle connections in the pool.
- */
- public void forceCloseAll() {
- lock.lock();
- try {
- expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(),
- dataSource.getPassword());
- for (int i = state.activeConnections.size(); i > 0; i--) {
- try {
- PooledConnection conn = state.activeConnections.remove(i - 1);
- conn.invalidate();
- Connection realConn = conn.getRealConnection();
- if (!realConn.getAutoCommit()) {
- realConn.rollback();
- }
- realConn.close();
- } catch (Exception e) {
- // ignore
- }
- }
- for (int i = state.idleConnections.size(); i > 0; i--) {
- try {
- PooledConnection conn = state.idleConnections.remove(i - 1);
- conn.invalidate();
- Connection realConn = conn.getRealConnection();
- if (!realConn.getAutoCommit()) {
- realConn.rollback();
- }
- realConn.close();
- } catch (Exception e) {
- // ignore
- }
- }
- } finally {
- lock.unlock();
- }
- if (log.isDebugEnabled()) {
- log.debug("PooledDataSource forcefully closed/removed all connections.");
- }
- }
- public PoolState getPoolState() {
- return state;
- }
- private int assembleConnectionTypeCode(String url, String username, String password) {
- return ("" + url + username + password).hashCode();
- }
- protected void pushConnection(PooledConnection conn) throws SQLException {
- lock.lock();
- try {
- state.activeConnections.remove(conn);
- if (conn.isValid()) {
- if (state.idleConnections.size() < poolMaximumIdleConnections
- && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
- state.accumulatedCheckoutTime += conn.getCheckoutTime();
- if (!conn.getRealConnection().getAutoCommit()) {
- conn.getRealConnection().rollback();
- }
- PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
- state.idleConnections.add(newConn);
- newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
- newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
- conn.invalidate();
- if (log.isDebugEnabled()) {
- log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
- }
- condition.signal();
- } else {
- state.accumulatedCheckoutTime += conn.getCheckoutTime();
- if (!conn.getRealConnection().getAutoCommit()) {
- conn.getRealConnection().rollback();
- }
- conn.getRealConnection().close();
- if (log.isDebugEnabled()) {
- log.debug("Closed connection " + conn.getRealHashCode() + ".");
- }
- conn.invalidate();
- }
- } else {
- if (log.isDebugEnabled()) {
- log.debug("A bad connection (" + conn.getRealHashCode()
- + ") attempted to return to the pool, discarding connection.");
- }
- state.badConnectionCount++;
- }
- } finally {
- lock.unlock();
- }
- }
- private PooledConnection popConnection(String username, String password) throws SQLException {
- boolean countedWait = false;
- PooledConnection conn = null;
- long t = System.currentTimeMillis();
- int localBadConnectionCount = 0;
- while (conn == null) {
- lock.lock();
- try {
- if (!state.idleConnections.isEmpty()) {
- // Pool has available connection
- conn = state.idleConnections.remove(0);
- if (log.isDebugEnabled()) {
- log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
- }
- } else if (state.activeConnections.size() < poolMaximumActiveConnections) {
- // Pool does not have available connection and can create a new connection
- conn = new PooledConnection(dataSource.getConnection(), this);
- if (log.isDebugEnabled()) {
- log.debug("Created connection " + conn.getRealHashCode() + ".");
- }
- } else {
- // Cannot create new connection
- PooledConnection oldestActiveConnection = state.activeConnections.get(0);
- long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
- if (longestCheckoutTime > poolMaximumCheckoutTime) {
- // Can claim overdue connection
- state.claimedOverdueConnectionCount++;
- state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
- state.accumulatedCheckoutTime += longestCheckoutTime;
- state.activeConnections.remove(oldestActiveConnection);
- if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
- try {
- oldestActiveConnection.getRealConnection().rollback();
- } catch (SQLException e) {
- /*
- * Just log a message for debug and continue to execute the following statement like nothing happened.
- * Wrap the bad connection with a new PooledConnection, this will help to not interrupt current
- * executing thread and give current thread a chance to join the next competition for another valid/good
- * database connection. At the end of this loop, bad {@link @conn} will be set as null.
- */
- log.debug("Bad connection. Could not roll back");
- }
- }
- conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
- conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
- conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
- oldestActiveConnection.invalidate();
- if (log.isDebugEnabled()) {
- log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
- }
- } else {
- // Must wait
- try {
- if (!countedWait) {
- state.hadToWaitCount++;
- countedWait = true;
- }
- if (log.isDebugEnabled()) {
- log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
- }
- long wt = System.currentTimeMillis();
- if (!condition.await(poolTimeToWait, TimeUnit.MILLISECONDS)) {
- log.debug("Wait failed...");
- }
- state.accumulatedWaitTime += System.currentTimeMillis() - wt;
- } catch (InterruptedException e) {
- // set interrupt flag
- Thread.currentThread().interrupt();
- break;
- }
- }
- }
- if (conn != null) {
- // ping to server and check the connection is valid or not
- if (conn.isValid()) {
- if (!conn.getRealConnection().getAutoCommit()) {
- conn.getRealConnection().rollback();
- }
- conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
- conn.setCheckoutTimestamp(System.currentTimeMillis());
- conn.setLastUsedTimestamp(System.currentTimeMillis());
- state.activeConnections.add(conn);
- state.requestCount++;
- state.accumulatedRequestTime += System.currentTimeMillis() - t;
- } else {
- if (log.isDebugEnabled()) {
- log.debug("A bad connection (" + conn.getRealHashCode()
- + ") was returned from the pool, getting another connection.");
- }
- state.badConnectionCount++;
- localBadConnectionCount++;
- conn = null;
- if (localBadConnectionCount > poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance) {
- if (log.isDebugEnabled()) {
- log.debug("PooledDataSource: Could not get a good connection to the database.");
- }
- throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
- }
- }
- }
- } finally {
- lock.unlock();
- }
- }
- if (conn == null) {
- if (log.isDebugEnabled()) {
- log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
- }
- throw new SQLException(
- "PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
- }
- return conn;
- }
- /**
- * Method to check to see if a connection is still usable
- *
- * @param conn
- * - the connection to check
- *
- * @return True if the connection is still usable
- */
- protected boolean pingConnection(PooledConnection conn) {
- boolean result;
- try {
- result = !conn.getRealConnection().isClosed();
- } catch (SQLException e) {
- if (log.isDebugEnabled()) {
- log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
- }
- result = false;
- }
- if (result && poolPingEnabled && poolPingConnectionsNotUsedFor >= 0
- && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
- try {
- if (log.isDebugEnabled()) {
- log.debug("Testing connection " + conn.getRealHashCode() + " ...");
- }
- Connection realConn = conn.getRealConnection();
- try (Statement statement = realConn.createStatement()) {
- statement.executeQuery(poolPingQuery).close();
- }
- if (!realConn.getAutoCommit()) {
- realConn.rollback();
- }
- if (log.isDebugEnabled()) {
- log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
- }
- } catch (Exception e) {
- log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());
- try {
- conn.getRealConnection().close();
- } catch (Exception e2) {
- // ignore
- }
- result = false;
- if (log.isDebugEnabled()) {
- log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
- }
- }
- }
- return result;
- }
- /**
- * Unwraps a pooled connection to get to the 'real' connection
- *
- * @param conn
- * - the pooled connection to unwrap
- *
- * @return The 'real' connection
- */
- public static Connection unwrapConnection(Connection conn) {
- if (Proxy.isProxyClass(conn.getClass())) {
- InvocationHandler handler = Proxy.getInvocationHandler(conn);
- if (handler instanceof PooledConnection) {
- return ((PooledConnection) handler).getRealConnection();
- }
- }
- return conn;
- }
- @Override
- protected void finalize() throws Throwable {
- forceCloseAll();
- super.finalize();
- }
- @Override
- public <T> T unwrap(Class<T> iface) throws SQLException {
- throw new SQLException(getClass().getName() + " is not a wrapper.");
- }
- @Override
- public boolean isWrapperFor(Class<?> iface) {
- return false;
- }
- @Override
- public Logger getParentLogger() {
- return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
- }
- }