This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch 9.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 6c17d912913502eb4f92461e24e31dda80086aaa Author: Mark Thomas <ma...@apache.org> AuthorDate: Wed Aug 26 16:15:50 2020 +0100 Update Commons DBCP to latest --- MERGE.txt | 2 +- .../apache/tomcat/dbcp/dbcp2/BasicDataSource.java | 187 +++++++++--------- .../tomcat/dbcp/dbcp2/BasicDataSourceFactory.java | 11 +- .../tomcat/dbcp/dbcp2/BasicDataSourceMXBean.java | 29 +++ .../tomcat/dbcp/dbcp2/DelegatingConnection.java | 4 +- .../tomcat/dbcp/dbcp2/PoolableConnection.java | 3 + .../dbcp/dbcp2/PoolableConnectionFactory.java | 14 ++ .../tomcat/dbcp/dbcp2/PoolingConnection.java | 220 +++++++++++---------- .../dbcp/dbcp2/cpdsadapter/DriverAdapterCPDS.java | 28 +-- .../dbcp2/datasources/CPDSConnectionFactory.java | 3 +- .../dbcp2/datasources/InstanceKeyDataSource.java | 8 +- .../dbcp/dbcp2/datasources/package-info.java | 2 +- .../dbcp/dbcp2/managed/BasicManagedDataSource.java | 3 +- .../dbcp2/managed/LocalXAConnectionFactory.java | 21 +- webapps/docs/changelog.xml | 4 + 15 files changed, 323 insertions(+), 216 deletions(-) diff --git a/MERGE.txt b/MERGE.txt index 79fc82e..b8c152d 100644 --- a/MERGE.txt +++ b/MERGE.txt @@ -69,4 +69,4 @@ Sub-tree src/main/java/org/apache/commons/dbcp2 src/main/resources/org/apache/commons/dbcp2 The SHA1 ID / tag for the most recent commit to be merged to Tomcat is: -a363906bf7a039f79c07fa3c68b082a69ae035d7 (2019-12-06) +6d232e547d5725e419832fc514fc0348aa897e7c (2020-08-11) diff --git a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSource.java b/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSource.java index 0293e9a..31faa61 100644 --- a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSource.java +++ b/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSource.java @@ -63,17 +63,6 @@ import org.apache.tomcat.dbcp.pool2.impl.GenericObjectPoolConfig; */ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBeanRegistration, AutoCloseable { - /** - * @since 2.0 - */ - private class PaGetConnection implements PrivilegedExceptionAction<Connection> { - - @Override - public Connection run() throws SQLException { - return createDataSource().getConnection(); - } - } - private static final Log log = LogFactory.getLog(BasicDataSource.class); static { @@ -220,6 +209,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean */ private boolean poolPreparedStatements = false; + private boolean clearStatementPoolOnReturn = false; + /** * <p> * The maximum number of open statements that can be allocated from the statement pool at the same time, or negative @@ -402,7 +393,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean * </p> * <p> * Attempts to acquire connections using {@link #getConnection()} after this method has been invoked result in - * SQLExceptions. + * SQLExceptions. To reopen a datasource that has been closed using this method, use {@link #start()}. * </p> * <p> * This method is idempotent - i.e., closing an already closed BasicDataSource has no effect and does not generate @@ -448,7 +439,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean } /** - * Creates a JDBC connection factory for this datasource. The JDBC driver is loaded using the following algorithm: + * Creates a JDBC connection factory for this data source. The JDBC driver is loaded using the following algorithm: * <ol> * <li>If a Driver instance has been specified via {@link #setDriver(Driver)} use it</li> * <li>If no Driver instance was specified and {@link #driverClassName} is specified that class is loaded using the @@ -471,6 +462,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean return ConnectionFactoryFactory.createConnectionFactory(this, DriverFactory.createDriver(this)); } + /** * Creates a connection pool for this datasource. This method only exists so subclasses can replace the * implementation class. @@ -530,7 +522,6 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean if (dataSource != null) { return dataSource; } - jmxRegister(); // create factory which returns raw physical connections @@ -544,10 +535,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean poolableConnectionFactory.setPoolStatements(poolPreparedStatements); poolableConnectionFactory.setMaxOpenPreparedStatements(maxOpenPreparedStatements); success = true; - } catch (final SQLException se) { + } catch (final SQLException | RuntimeException se) { throw se; - } catch (final RuntimeException rte) { - throw rte; } catch (final Exception ex) { throw new SQLException("Error creating connection factory", ex); } @@ -564,10 +553,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean newDataSource = createDataSourceInstance(); newDataSource.setLogWriter(logWriter); success = true; - } catch (final SQLException se) { + } catch (final SQLException | RuntimeException se) { throw se; - } catch (final RuntimeException rte) { - throw rte; } catch (final Exception ex) { throw new SQLException("Error creating datasource", ex); } finally { @@ -654,6 +641,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean connectionFactory.setDefaultSchema(defaultSchema); connectionFactory.setCacheState(cacheState); connectionFactory.setPoolStatements(poolPreparedStatements); + connectionFactory.setClearStatementPoolOnReturn(clearStatementPoolOnReturn); connectionFactory.setMaxOpenPreparedStatements(maxOpenPreparedStatements); connectionFactory.setMaxConnLifetimeMillis(maxConnLifetimeMillis); connectionFactory.setRollbackOnReturn(getRollbackOnReturn()); @@ -687,10 +675,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean * @return The print writer used by this configuration to log information on abandoned objects. */ public PrintWriter getAbandonedLogWriter() { - if (abandonedConfig != null) { - return abandonedConfig.getLogWriter(); - } - return null; + return abandonedConfig == null ? null : abandonedConfig.getLogWriter(); } /** @@ -702,10 +687,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean */ @Override public boolean getAbandonedUsageTracking() { - if (abandonedConfig != null) { - return abandonedConfig.getUseUsageTracking(); - } - return false; + return abandonedConfig == null ? false : abandonedConfig.getUseUsageTracking(); } /** @@ -738,7 +720,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean @Override public Connection getConnection() throws SQLException { if (Utils.IS_SECURITY_ENABLED) { - final PrivilegedExceptionAction<Connection> action = new PaGetConnection(); + final PrivilegedExceptionAction<Connection> action = () -> createDataSource().getConnection(); try { return AccessController.doPrivileged(action); } catch (final PrivilegedActionException e) { @@ -790,10 +772,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean */ public List<String> getConnectionInitSqls() { final List<String> result = connectionInitSqls; - if (result == null) { - return Collections.emptyList(); - } - return result; + return result == null ? Collections.emptyList() : result; } /** @@ -884,10 +863,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean */ public Set<String> getDisconnectionSqlCodes() { final Set<String> result = disconnectionSqlCodes; - if (result == null) { - return Collections.emptySet(); - } - return result; + return result == null ? Collections.emptySet() : result; } /** @@ -1021,10 +997,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean */ @Override public boolean getLogAbandoned() { - if (abandonedConfig != null) { - return abandonedConfig.getLogAbandoned(); - } - return false; + return abandonedConfig == null ? false : abandonedConfig.getLogAbandoned(); } /** @@ -1055,8 +1028,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean */ @Override public int getLoginTimeout() throws SQLException { - // This method isn't supported by the PoolingDataSource returned by the - // createDataSource + // This method isn't supported by the PoolingDataSource returned by the createDataSource throw new UnsupportedOperationException("Not supported by BasicDataSource"); } @@ -1170,10 +1142,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean public int getNumActive() { // Copy reference to avoid NPE if close happens after null check final GenericObjectPool<PoolableConnection> pool = connectionPool; - if (pool != null) { - return pool.getNumActive(); - } - return 0; + return pool == null ? 0 : pool.getNumActive(); } /** @@ -1185,10 +1154,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean public int getNumIdle() { // Copy reference to avoid NPE if close happens after null check final GenericObjectPool<PoolableConnection> pool = connectionPool; - if (pool != null) { - return pool.getNumIdle(); - } - return 0; + return pool == null ? 0 : pool.getNumIdle(); } /** @@ -1246,10 +1212,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean */ @Override public boolean getRemoveAbandonedOnBorrow() { - if (abandonedConfig != null) { - return abandonedConfig.getRemoveAbandonedOnBorrow(); - } - return false; + return abandonedConfig == null ? false : abandonedConfig.getRemoveAbandonedOnBorrow(); } /** @@ -1270,10 +1233,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean */ @Override public boolean getRemoveAbandonedOnMaintenance() { - if (abandonedConfig != null) { - return abandonedConfig.getRemoveAbandonedOnMaintenance(); - } - return false; + return abandonedConfig == null ? false : abandonedConfig.getRemoveAbandonedOnMaintenance(); } /** @@ -1298,10 +1258,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean */ @Override public int getRemoveAbandonedTimeout() { - if (abandonedConfig != null) { - return abandonedConfig.getRemoveAbandonedTimeout(); - } - return 300; + return abandonedConfig == null ? 300 : abandonedConfig.getRemoveAbandonedTimeout(); } /** @@ -1478,7 +1435,18 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean } /** - * If true, this data source is closed and no more connections can be retrieved from this datasource. + * Returns true if the statement pool is cleared when the connection is returned to its pool. + * + * @return true if the statement pool is cleared at connection return + * @since 2.8.0 + */ + @Override + public boolean isClearStatementPoolOnReturn() { + return clearStatementPoolOnReturn; + } + + /** + * If true, this data source is closed and no more connections can be retrieved from this data source. * * @return true, if the data source is closed; false otherwise */ @@ -1591,6 +1559,31 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean } /** + * Restarts the datasource. + * <p> + * This method calls {@link #close()} and {@link #start()} in sequence within synchronized scope so any + * connection requests that come in while the datsource is shutting down will be served by the new pool. + * <p> + * Idle connections that are stored in the connection pool when this method is invoked are closed, but + * connections that are checked out to clients when this method is invoked are not affected. When client + * applications subsequently invoke {@link Connection#close()} to return these connections to the pool, the + * underlying JDBC connections are closed. These connections do not count in {@link #getMaxTotal()} or + * {@link #getNumActive()} after invoking this method. For example, if there are 3 connections checked out by + * clients when {@link #restart()} is invoked, after this method is called, {@link #getNumActive()} will + * return 0 and up to {@link #getMaxTotal()} + 3 connections may be open until the connections sourced from + * the original pool are returned. + * <p> + * The new connection pool created by this method is initialized with currently set configuration properties. + * + * @throws SQLException if an error occurs initializing the datasource + */ + @Override + public synchronized void restart() throws SQLException { + close(); + start(); + } + + /** * Sets the print writer to be used by this configuration to log information on abandoned objects. * * @param logWriter The new log writer @@ -1655,8 +1648,6 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean this.autoCommitOnReturn = autoCommitOnReturn; } - // ----------------------------------------------------- DataSource Methods - /** * Sets the state caching flag. * @@ -1667,17 +1658,24 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean } /** + * Sets whether the pool of statements (which was enabled with {@link #setPoolPreparedStatements(boolean)}) should + * be cleared when the connection is returned to its pool. Default is false. + * + * @param clearStatementPoolOnReturn clear or not + * @since 2.8.0 + */ + public void setClearStatementPoolOnReturn(final boolean clearStatementPoolOnReturn) { + this.clearStatementPoolOnReturn = clearStatementPoolOnReturn; + } + + /** * Sets the ConnectionFactory class name. * * @param connectionFactoryClassName A class name. * @since 2.7.0 */ public void setConnectionFactoryClassName(final String connectionFactoryClassName) { - if (isEmpty(connectionFactoryClassName)) { - this.connectionFactoryClassName = null; - } else { - this.connectionFactoryClassName = connectionFactoryClassName; - } + this.connectionFactoryClassName = isEmpty(connectionFactoryClassName) ? null : connectionFactoryClassName; } /** @@ -1768,11 +1766,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean * @param defaultCatalog the default catalog */ public void setDefaultCatalog(final String defaultCatalog) { - if (isEmpty(defaultCatalog)) { - this.defaultCatalog = null; - } else { - this.defaultCatalog = defaultCatalog; - } + this.defaultCatalog = isEmpty(defaultCatalog) ? null : defaultCatalog; } /** @@ -1815,11 +1809,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean * @since 2.5.0 */ public void setDefaultSchema(final String defaultSchema) { - if (isEmpty(defaultSchema)) { - this.defaultSchema = null; - } else { - this.defaultSchema = defaultSchema; - } + this.defaultSchema = isEmpty(defaultSchema) ? null : defaultSchema; } /** @@ -1920,11 +1910,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean * @param driverClassName the class name of the JDBC driver */ public synchronized void setDriverClassName(final String driverClassName) { - if (isEmpty(driverClassName)) { - this.driverClassName = null; - } else { - this.driverClassName = driverClassName; - } + this.driverClassName = isEmpty(driverClassName) ? null : driverClassName; } /** @@ -2409,11 +2395,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean * @param validationQuery the new value for the validation query */ public void setValidationQuery(final String validationQuery) { - if (isEmpty(validationQuery)) { - this.validationQuery = null; - } else { - this.validationQuery = validationQuery; - } + this.validationQuery = isEmpty(validationQuery) ? null : validationQuery; } /** @@ -2432,6 +2414,29 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean } /** + * Starts the datasource. + * <p> + * It is not necessary to call this method before using a newly created BasicDataSource instance, but + * calling it in that context causes the datasource to be immediately initialized (instead of waiting for + * the first {@link #getConnection()} request). Its primary use is to restart and reinitialize a + * datasource that has been closed. + * <p> + * When this method is called after {@link #close()}, connections checked out by clients + * before the datasource was stopped do not count in {@link #getMaxTotal()} or {@link #getNumActive()}. + * For example, if there are 3 connections checked out by clients when {@link #close()} is invoked and they are + * not returned before {@link #start()} is invoked, after this method is called, {@link #getNumActive()} will + * return 0. These connections will be physically closed when they are returned, but they will not count against + * the maximum allowed in the newly started datasource. + * + * @throws SQLException if an error occurs initializing the datasource + */ + @Override + public synchronized void start() throws SQLException { + closed = false; + createDataSource(); + } + + /** * Starts the connection pool maintenance task, if configured. */ protected void startPoolMaintenance() { diff --git a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceFactory.java index 158b944..b8ae8f2 100644 --- a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceFactory.java +++ b/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceFactory.java @@ -100,6 +100,7 @@ public class BasicDataSourceFactory implements ObjectFactory { private static final String PROP_LOG_ABANDONED = "logAbandoned"; private static final String PROP_ABANDONED_USAGE_TRACKING = "abandonedUsageTracking"; private static final String PROP_POOL_PREPARED_STATEMENTS = "poolPreparedStatements"; + private static final String PROP_CLEAR_STATEMENT_POOL_ON_RETURN = "clearStatementPoolOnReturn"; private static final String PROP_MAX_OPEN_PREPARED_STATEMENTS = "maxOpenPreparedStatements"; private static final String PROP_CONNECTION_PROPERTIES = "connectionProperties"; private static final String PROP_MAX_CONN_LIFETIME_MILLIS = "maxConnLifetimeMillis"; @@ -140,6 +141,7 @@ public class BasicDataSourceFactory implements ObjectFactory { PROP_URL, PROP_USER_NAME, PROP_VALIDATION_QUERY, PROP_VALIDATION_QUERY_TIMEOUT, PROP_CONNECTION_INIT_SQLS, PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED, PROP_REMOVE_ABANDONED_ON_BORROW, PROP_REMOVE_ABANDONED_ON_MAINTENANCE, PROP_REMOVE_ABANDONED_TIMEOUT, PROP_LOG_ABANDONED, PROP_ABANDONED_USAGE_TRACKING, PROP_POOL_PREPARED_STATEMENTS, + PROP_CLEAR_STATEMENT_POOL_ON_RETURN, PROP_MAX_OPEN_PREPARED_STATEMENTS, PROP_CONNECTION_PROPERTIES, PROP_MAX_CONN_LIFETIME_MILLIS, PROP_LOG_EXPIRED_CONNECTIONS, PROP_ROLLBACK_ON_RETURN, PROP_ENABLE_AUTO_COMMIT_ON_RETURN, PROP_DEFAULT_QUERY_TIMEOUT, PROP_FAST_FAIL_VALIDATION, PROP_DISCONNECTION_SQL_CODES, PROP_JMX_NAME, @@ -255,7 +257,7 @@ public class BasicDataSourceFactory implements ObjectFactory { final List<String> infoMessages) { final List<String> allPropsAsList = Arrays.asList(ALL_PROPERTIES); final String nameString = name != null ? "Name = " + name.toString() + " " : ""; - if (NUPROP_WARNTEXT != null && !NUPROP_WARNTEXT.keySet().isEmpty()) { + if (NUPROP_WARNTEXT != null && !NUPROP_WARNTEXT.isEmpty()) { for (final String propertyName : NUPROP_WARNTEXT.keySet()) { final RefAddr ra = ref.get(propertyName); if (ra != null && !allPropsAsList.contains(ra.getType())) { @@ -275,7 +277,7 @@ public class BasicDataSourceFactory implements ObjectFactory { final String propertyName = ra.getType(); // If property name is not in the properties list, we haven't warned on it // and it is not in the "silent" list, tell user we are ignoring it. - if (!(allPropsAsList.contains(propertyName) || NUPROP_WARNTEXT.keySet().contains(propertyName) + if (!(allPropsAsList.contains(propertyName) || NUPROP_WARNTEXT.containsKey(propertyName) || SILENT_PROPERTIES.contains(propertyName))) { final String propertyValue = ra.getContent().toString(); final StringBuilder stringBuilder = new StringBuilder(nameString); @@ -490,6 +492,11 @@ public class BasicDataSourceFactory implements ObjectFactory { dataSource.setPoolPreparedStatements(Boolean.valueOf(value).booleanValue()); } + value = properties.getProperty(PROP_CLEAR_STATEMENT_POOL_ON_RETURN); + if (value != null) { + dataSource.setClearStatementPoolOnReturn(Boolean.valueOf(value).booleanValue()); + } + value = properties.getProperty(PROP_MAX_OPEN_PREPARED_STATEMENTS); if (value != null) { dataSource.setMaxOpenPreparedStatements(Integer.parseInt(value)); diff --git a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceMXBean.java b/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceMXBean.java index 687786e..5dc6b7a 100644 --- a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceMXBean.java +++ b/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceMXBean.java @@ -16,6 +16,8 @@ */ package org.apache.tomcat.dbcp.dbcp2; +import java.sql.SQLException; + /** * Defines the methods that will be made available via JMX. * @@ -132,6 +134,16 @@ public interface BasicDataSourceMXBean { boolean isPoolPreparedStatements(); /** + * See {@link BasicDataSource#isClearStatementPoolOnReturn()} + * + * @return {@link BasicDataSource#isClearStatementPoolOnReturn()} + * @since 2.8.0 + */ + default boolean isClearStatementPoolOnReturn() { + return false; + } + + /** * See {@link BasicDataSource#getMaxOpenPreparedStatements()} * * @return {@link BasicDataSource#getMaxOpenPreparedStatements()} @@ -315,4 +327,21 @@ public interface BasicDataSourceMXBean { * @since 2.1 */ String[] getDisconnectionSqlCodesAsArray(); + + /** + * See {@link BasicDataSource#start()} + * + * @throws SQLException if an error occurs initializing the datasource + * + * @since 2.8 + */ + default void start() throws SQLException {} + + /** + * See {@link BasicDataSource#restart()} + * @throws SQLException if an error occurs initializing the datasource + * + * @since 2.8 + */ + default void restart() throws SQLException {} } diff --git a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingConnection.java b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingConnection.java index c215d03..45e4dd6 100644 --- a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingConnection.java +++ b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingConnection.java @@ -532,7 +532,7 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i try { connection.setAutoCommit(autoCommit); if (cacheState) { - autoCommitCached = Boolean.valueOf(autoCommit); + autoCommitCached = Boolean.valueOf(connection.getAutoCommit()); } } catch (final SQLException e) { autoCommitCached = null; @@ -556,7 +556,7 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i try { connection.setReadOnly(readOnly); if (cacheState) { - readOnlyCached = Boolean.valueOf(readOnly); + readOnlyCached = Boolean.valueOf(connection.isReadOnly()); } } catch (final SQLException e) { readOnlyCached = null; diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnection.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnection.java index 9c15c7e..bdca159 100644 --- a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnection.java +++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnection.java @@ -124,6 +124,9 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme protected void passivate() throws SQLException { super.passivate(); setClosedInternal(true); + if (getDelegateInternal() instanceof PoolingConnection) { + ((PoolingConnection) getDelegateInternal()).connectionReturnedToPool(); + } } /** diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionFactory.java index 4123d16..b25bb9a 100644 --- a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionFactory.java +++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionFactory.java @@ -84,6 +84,8 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo private boolean poolStatements; + private boolean clearStatementPoolOnReturn; + private int maxOpenPreparedStatements = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY; private long maxConnLifetimeMillis = -1; @@ -392,6 +394,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool = new GenericKeyedObjectPool<>( poolingConn, config); poolingConn.setStatementPool(stmtPool); + poolingConn.setClearStatementPoolOnReturn(clearStatementPoolOnReturn); poolingConn.setCacheState(cacheState); } @@ -597,6 +600,17 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo this.poolStatements = poolStatements; } + /** + * Sets whether the pool of statements (which was enabled with {@link #setPoolStatements(boolean)}) should + * be cleared when the connection is returned to its pool. Default is false. + * + * @param clearStatementPoolOnReturn clear or not + * @since 2.8.0 + */ + public void setClearStatementPoolOnReturn(final boolean clearStatementPoolOnReturn) { + this.clearStatementPoolOnReturn = clearStatementPoolOnReturn; + } + public void setRollbackOnReturn(final boolean rollbackOnReturn) { this.rollbackOnReturn = rollbackOnReturn; } diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolingConnection.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolingConnection.java index 86e6d4e..8615074 100644 --- a/java/org/apache/tomcat/dbcp/dbcp2/PoolingConnection.java +++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolingConnection.java @@ -65,6 +65,8 @@ public class PoolingConnection extends DelegatingConnection<Connection> /** Pool of {@link PreparedStatement}s. and {@link CallableStatement}s */ private KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> pstmtPool; + private boolean clearStatementPoolOnReturn = false; + /** * Constructor. * @@ -117,6 +119,23 @@ public class PoolingConnection extends DelegatingConnection<Connection> } /** + * Notification from {@link PoolableConnection} that we returned to the pool. + * + * @throws SQLException when <code>clearStatementPoolOnReturn</code> is true and the statement pool could not be + * cleared + * @since 2.8.0 + */ + public void connectionReturnedToPool() throws SQLException { + if (pstmtPool != null && clearStatementPoolOnReturn) { + try { + pstmtPool.clear(); + } catch (Exception e) { + throw new SQLException("Error clearing statement pool", e); + } + } + } + + /** * Creates a PStmtKey for the given arguments. * * @param sql @@ -134,7 +153,8 @@ public class PoolingConnection extends DelegatingConnection<Connection> * @param sql * the SQL string used to define the statement * @param columnIndexes - * column indexes + * An array of column indexes indicating the columns that should be returned from the inserted row or + * rows. * * @return the PStmtKey created for the given arguments. */ @@ -142,6 +162,17 @@ public class PoolingConnection extends DelegatingConnection<Connection> return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnIndexes); } + /** + * Creates a PStmtKey for the given arguments. + * + * @param sql + * the SQL string used to define the statement + * @param autoGeneratedKeys + * A flag indicating whether auto-generated keys should be returned; one of + * <code>Statement.RETURN_GENERATED_KEYS</code> or <code>Statement.NO_GENERATED_KEYS</code>. + * + * @return the PStmtKey created for the given arguments. + */ protected PStmtKey createKey(final String sql, final int autoGeneratedKeys) { return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), autoGeneratedKeys); } @@ -277,13 +308,23 @@ public class PoolingConnection extends DelegatingConnection<Connection> } private String getSchemaOrNull() { - String catalog = null; + String schema = null; try { - catalog = getSchema(); + schema = getSchema(); } catch (final SQLException e) { // Ignored } - return catalog; + return schema; + } + + /** + * Returns the prepared statement pool we're using. + * + * @return statement pool + * @since 2.8.0 + */ + public KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> getStatementPool() { + return pstmtPool; } /** @@ -342,6 +383,19 @@ public class PoolingConnection extends DelegatingConnection<Connection> /** * Creates or obtains a {@link CallableStatement} from the pool. * + * @param key + * a {@link PStmtKey} for the given arguments + * @return a {@link PoolableCallableStatement} + * @throws SQLException + * Wraps an underlying exception. + */ + private CallableStatement prepareCall(final PStmtKey key) throws SQLException { + return (CallableStatement) prepareStatement(key); + } + + /** + * Creates or obtains a {@link CallableStatement} from the pool. + * * @param sql * the SQL string used to define the CallableStatement * @return a {@link PoolableCallableStatement} @@ -350,15 +404,7 @@ public class PoolingConnection extends DelegatingConnection<Connection> */ @Override public CallableStatement prepareCall(final String sql) throws SQLException { - try { - return (CallableStatement) pstmtPool.borrowObject(createKey(sql, StatementType.CALLABLE_STATEMENT)); - } catch (final NoSuchElementException e) { - throw new SQLException("MaxOpenCallableStatements limit reached", e); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Borrow callableStatement from pool failed", e); - } + return prepareCall(createKey(sql, StatementType.CALLABLE_STATEMENT)); } /** @@ -377,16 +423,7 @@ public class PoolingConnection extends DelegatingConnection<Connection> @Override public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency) throws SQLException { - try { - return (CallableStatement) pstmtPool.borrowObject( - createKey(sql, resultSetType, resultSetConcurrency, StatementType.CALLABLE_STATEMENT)); - } catch (final NoSuchElementException e) { - throw new SQLException("MaxOpenCallableStatements limit reached", e); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Borrow callableStatement from pool failed", e); - } + return prepareCall(createKey(sql, resultSetType, resultSetConcurrency, StatementType.CALLABLE_STATEMENT)); } /** @@ -407,32 +444,25 @@ public class PoolingConnection extends DelegatingConnection<Connection> @Override public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) throws SQLException { - try { - return (CallableStatement) pstmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency, - resultSetHoldability, StatementType.CALLABLE_STATEMENT)); - } catch (final NoSuchElementException e) { - throw new SQLException("MaxOpenCallableStatements limit reached", e); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Borrow callableStatement from pool failed", e); - } + return prepareCall(createKey(sql, resultSetType, resultSetConcurrency, + resultSetHoldability, StatementType.CALLABLE_STATEMENT)); } /** * Creates or obtains a {@link PreparedStatement} from the pool. * - * @param sql - * the SQL string used to define the PreparedStatement + * @param key + * a {@link PStmtKey} for the given arguments * @return a {@link PoolablePreparedStatement} + * @throws SQLException + * Wraps an underlying exception. */ - @Override - public PreparedStatement prepareStatement(final String sql) throws SQLException { + private PreparedStatement prepareStatement(final PStmtKey key) throws SQLException { if (null == pstmtPool) { throw new SQLException("Statement pool is null - closed or invalid PoolingConnection."); } try { - return pstmtPool.borrowObject(createKey(sql)); + return pstmtPool.borrowObject(key); } catch (final NoSuchElementException e) { throw new SQLException("MaxOpenPreparedStatements limit reached", e); } catch (final RuntimeException e) { @@ -442,20 +472,35 @@ public class PoolingConnection extends DelegatingConnection<Connection> } } + /** + * Creates or obtains a {@link PreparedStatement} from the pool. + * + * @param sql + * the SQL string used to define the PreparedStatement + * @return a {@link PoolablePreparedStatement} + * @throws SQLException + * Wraps an underlying exception. + */ + @Override + public PreparedStatement prepareStatement(final String sql) throws SQLException { + return prepareStatement(createKey(sql)); + } + + /* + * Creates or obtains a {@link PreparedStatement} from the pool. + * + * @param sql + * the SQL string used to define the PreparedStatement + * @param autoGeneratedKeys + * A flag indicating whether auto-generated keys should be returned; one of + * <code>Statement.RETURN_GENERATED_KEYS</code> or <code>Statement.NO_GENERATED_KEYS</code>. + * @return a {@link PoolablePreparedStatement} + * @throws SQLException + * Wraps an underlying exception. + */ @Override public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException { - if (null == pstmtPool) { - throw new SQLException("Statement pool is null - closed or invalid PoolingConnection."); - } - try { - return pstmtPool.borrowObject(createKey(sql, autoGeneratedKeys)); - } catch (final NoSuchElementException e) { - throw new SQLException("MaxOpenPreparedStatements limit reached", e); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Borrow prepareStatement from pool failed", e); - } + return prepareStatement(createKey(sql, autoGeneratedKeys)); } /** @@ -464,23 +509,16 @@ public class PoolingConnection extends DelegatingConnection<Connection> * @param sql * the SQL string used to define the PreparedStatement * @param columnIndexes - * column indexes + * An array of column indexes indicating the columns that should be returned from the inserted row or + * rows. * @return a {@link PoolablePreparedStatement} + * @throws SQLException + * Wraps an underlying exception. + * */ @Override public PreparedStatement prepareStatement(final String sql, final int columnIndexes[]) throws SQLException { - if (null == pstmtPool) { - throw new SQLException("Statement pool is null - closed or invalid PoolingConnection."); - } - try { - return pstmtPool.borrowObject(createKey(sql, columnIndexes)); - } catch (final NoSuchElementException e) { - throw new SQLException("MaxOpenPreparedStatements limit reached", e); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Borrow prepareStatement from pool failed", e); - } + return prepareStatement(createKey(sql, columnIndexes)); } /** @@ -493,22 +531,13 @@ public class PoolingConnection extends DelegatingConnection<Connection> * @param resultSetConcurrency * result set concurrency * @return a {@link PoolablePreparedStatement} + * @throws SQLException + * Wraps an underlying exception. */ @Override public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency) throws SQLException { - if (null == pstmtPool) { - throw new SQLException("Statement pool is null - closed or invalid PoolingConnection."); - } - try { - return pstmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency)); - } catch (final NoSuchElementException e) { - throw new SQLException("MaxOpenPreparedStatements limit reached", e); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Borrow prepareStatement from pool failed", e); - } + return prepareStatement(createKey(sql, resultSetType, resultSetConcurrency)); } /** @@ -523,22 +552,13 @@ public class PoolingConnection extends DelegatingConnection<Connection> * @param resultSetHoldability * result set holdability * @return a {@link PoolablePreparedStatement} + * @throws SQLException + * Wraps an underlying exception. */ @Override public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) throws SQLException { - if (null == pstmtPool) { - throw new SQLException("Statement pool is null - closed or invalid PoolingConnection."); - } - try { - return pstmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency, resultSetHoldability)); - } catch (final NoSuchElementException e) { - throw new SQLException("MaxOpenPreparedStatements limit reached", e); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Borrow prepareStatement from pool failed", e); - } + return prepareStatement(createKey(sql, resultSetType, resultSetConcurrency, resultSetHoldability)); } /** @@ -549,21 +569,23 @@ public class PoolingConnection extends DelegatingConnection<Connection> * @param columnNames * column names * @return a {@link PoolablePreparedStatement} + * @throws SQLException + * Wraps an underlying exception. */ @Override public PreparedStatement prepareStatement(final String sql, final String columnNames[]) throws SQLException { - if (null == pstmtPool) { - throw new SQLException("Statement pool is null - closed or invalid PoolingConnection."); - } - try { - return pstmtPool.borrowObject(createKey(sql, columnNames)); - } catch (final NoSuchElementException e) { - throw new SQLException("MaxOpenPreparedStatements limit reached", e); - } catch (final RuntimeException e) { - throw e; - } catch (final Exception e) { - throw new SQLException("Borrow prepareStatement from pool failed", e); - } + return prepareStatement(createKey(sql, columnNames)); + } + + /** + * Sets whether the pool of statements should be cleared when the connection is returned to its pool. + * Default is false. + * + * @param clearStatementPoolOnReturn clear or not + * @since 2.8.0 + */ + public void setClearStatementPoolOnReturn(final boolean clearStatementPoolOnReturn) { + this.clearStatementPoolOnReturn = clearStatementPoolOnReturn; } /** diff --git a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/DriverAdapterCPDS.java b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/DriverAdapterCPDS.java index 34bad40..772682f 100644 --- a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/DriverAdapterCPDS.java +++ b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/DriverAdapterCPDS.java @@ -37,6 +37,7 @@ import javax.naming.spi.ObjectFactory; import javax.sql.ConnectionPoolDataSource; import javax.sql.PooledConnection; +import org.apache.tomcat.dbcp.dbcp2.BasicDataSource; import org.apache.tomcat.dbcp.dbcp2.DelegatingPreparedStatement; import org.apache.tomcat.dbcp.dbcp2.PStmtKey; import org.apache.tomcat.dbcp.dbcp2.Utils; @@ -621,17 +622,15 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl /** * Sets the number of statements to examine during each run of the idle object evictor thread (if any). * <p> - * When a negative value is supplied, <code>ceil({*link #numIdle})/abs({*link #getNumTestsPerEvictionRun})</code> - * tests will be run. I.e., when the value is <i>-n</i>, roughly one <i>n</i>th of the idle objects will be tested - * per run. + * When a negative value is supplied, + * <code>ceil({@link BasicDataSource#getNumIdle})/abs({@link #getNumTestsPerEvictionRun})</code> tests will be run. + * I.e., when the value is <i>-n</i>, roughly one <i>n</i>th of the idle objects will be tested per run. * </p> * - * @param numTestsPerEvictionRun - * number of statements to examine per run + * @param numTestsPerEvictionRun number of statements to examine per run * @see #getNumTestsPerEvictionRun() * @see #setTimeBetweenEvictionRunsMillis(long) - * @throws IllegalStateException - * if {@link #getPooledConnection()} has been called + * @throws IllegalStateException if {@link #getPooledConnection()} has been called */ public void setNumTestsPerEvictionRun(final int numTestsPerEvictionRun) { assertInitializationAllowed(); @@ -755,11 +754,8 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl builder.append(getConnectionCalled); builder.append(", connectionProperties="); Properties tmpProps = connectionProperties; - final String pwdKey = "password"; - if (connectionProperties != null && connectionProperties.contains(pwdKey)) { - tmpProps = (Properties) connectionProperties.clone(); - tmpProps.remove(pwdKey); - } + tmpProps = mask(tmpProps, "user"); + tmpProps = mask(tmpProps, "password"); builder.append(tmpProps); builder.append(", accessToUnderlyingConnectionAllowed="); builder.append(accessToUnderlyingConnectionAllowed); @@ -767,6 +763,14 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl return builder.toString(); } + private Properties mask(Properties properties, final String maskValueAtKey) { + if (connectionProperties != null && connectionProperties.contains(maskValueAtKey)) { + properties = (Properties) connectionProperties.clone(); + properties.put(maskValueAtKey, "[" + maskValueAtKey + "]"); + } + return properties; + } + private void update(final Properties properties, final String key, final String value) { if (properties != null && key != null) { if (value == null) { diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/CPDSConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/CPDSConnectionFactory.java index 915e4cd..93363a4 100644 --- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/CPDSConnectionFactory.java +++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/CPDSConnectionFactory.java @@ -37,8 +37,7 @@ import org.apache.tomcat.dbcp.pool2.PooledObjectFactory; import org.apache.tomcat.dbcp.pool2.impl.DefaultPooledObject; /** - * A {@link PooledObjectFactory} that creates - * {@link org.apache.tomcat.dbcp.dbcp2.PoolableConnection PoolableConnection}s. + * A {@link PooledObjectFactory} that creates {@link org.apache.tomcat.dbcp.dbcp2.PoolableConnection PoolableConnection}s. * * @since 2.0 */ diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSource.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSource.java index 69d6eff..8168bcc 100644 --- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSource.java +++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSource.java @@ -532,7 +532,7 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable } /** - * Sets the backend ConnectionPoolDataSource. This property should not be set if using JNDI to access the + * Sets the back end ConnectionPoolDataSource. This property should not be set if using JNDI to access the * data source. * * @param v @@ -689,7 +689,7 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable /** * Gets the value of jndiEnvironment which is used when instantiating a JNDI InitialContext. This InitialContext is - * used to locate the backend ConnectionPoolDataSource. + * used to locate the back end ConnectionPoolDataSource. * * @param key * JNDI environment key. @@ -705,7 +705,7 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable /** * Sets the value of the given JNDI environment property to be used when instantiating a JNDI InitialContext. This - * InitialContext is used to locate the backend ConnectionPoolDataSource. + * InitialContext is used to locate the back end ConnectionPoolDataSource. * * @param key * the JNDI environment property to set. @@ -721,7 +721,7 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable /** * Sets the JNDI environment to be used when instantiating a JNDI InitialContext. This InitialContext is used to - * locate the backend ConnectionPoolDataSource. + * locate the back end ConnectionPoolDataSource. * * @param properties * the JNDI environment property to set which will overwrite any current settings diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/package-info.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/package-info.java index 6395e29..531db68 100644 --- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/package-info.java +++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/package-info.java @@ -127,7 +127,7 @@ * Connection pooling is useful in applications regardless of whether they run * in a J2EE environment and a <code>DataSource</code> can be used within a * simpler environment. The example below shows SharedPoolDataSource using - * DriverAdapterCPDS as the backend source, though any CPDS is applicable. + * DriverAdapterCPDS as the back end source, though any CPDS is applicable. * </p> * * <code> diff --git a/java/org/apache/tomcat/dbcp/dbcp2/managed/BasicManagedDataSource.java b/java/org/apache/tomcat/dbcp/dbcp2/managed/BasicManagedDataSource.java index 654edc2..9129598 100644 --- a/java/org/apache/tomcat/dbcp/dbcp2/managed/BasicManagedDataSource.java +++ b/java/org/apache/tomcat/dbcp/dbcp2/managed/BasicManagedDataSource.java @@ -174,7 +174,7 @@ public class BasicManagedDataSource extends BasicDataSource { if (xaDataSource == null) { final ConnectionFactory connectionFactory = super.createConnectionFactory(); final XAConnectionFactory xaConnectionFactory = new LocalXAConnectionFactory(getTransactionManager(), - connectionFactory); + getTransactionSynchronizationRegistry(), connectionFactory); transactionRegistry = xaConnectionFactory.getTransactionRegistry(); return xaConnectionFactory; } @@ -237,6 +237,7 @@ public class BasicManagedDataSource extends BasicDataSource { connectionFactory.setDefaultSchema(getDefaultSchema()); connectionFactory.setCacheState(getCacheState()); connectionFactory.setPoolStatements(isPoolPreparedStatements()); + connectionFactory.setClearStatementPoolOnReturn(isClearStatementPoolOnReturn()); connectionFactory.setMaxOpenPreparedStatements(getMaxOpenPreparedStatements()); connectionFactory.setMaxConnLifetimeMillis(getMaxConnLifetimeMillis()); connectionFactory.setRollbackOnReturn(getRollbackOnReturn()); diff --git a/java/org/apache/tomcat/dbcp/dbcp2/managed/LocalXAConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/managed/LocalXAConnectionFactory.java index d5e176a..a50b7ed 100644 --- a/java/org/apache/tomcat/dbcp/dbcp2/managed/LocalXAConnectionFactory.java +++ b/java/org/apache/tomcat/dbcp/dbcp2/managed/LocalXAConnectionFactory.java @@ -22,6 +22,7 @@ import java.sql.SQLException; import java.util.Objects; import javax.transaction.TransactionManager; +import javax.transaction.TransactionSynchronizationRegistry; import javax.transaction.xa.XAException; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; @@ -316,9 +317,27 @@ public class LocalXAConnectionFactory implements XAConnectionFactory { */ public LocalXAConnectionFactory(final TransactionManager transactionManager, final ConnectionFactory connectionFactory) { + this(transactionManager, null, connectionFactory); + } + + /** + * Creates an LocalXAConnectionFactory which uses the specified connection factory to create database connections. + * The connections are enlisted into transactions using the specified transaction manager. + * + * @param transactionManager + * the transaction manager in which connections will be enlisted + * @param transactionSynchronizationRegistry + * the optional TSR to register synchronizations with + * @param connectionFactory + * the connection factory from which connections will be retrieved + * @since 2.8.0 + */ + public LocalXAConnectionFactory(final TransactionManager transactionManager, + final TransactionSynchronizationRegistry transactionSynchronizationRegistry, + final ConnectionFactory connectionFactory) { Objects.requireNonNull(transactionManager, "transactionManager is null"); Objects.requireNonNull(connectionFactory, "connectionFactory is null"); - this.transactionRegistry = new TransactionRegistry(transactionManager); + this.transactionRegistry = new TransactionRegistry(transactionManager, transactionSynchronizationRegistry); this.connectionFactory = connectionFactory; } diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 0558539..6cf117a 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -210,6 +210,10 @@ Update the internal fork of Apache Commons Pool to 2.8.1. Code clean-up and improved abandoned pool handling. (markt) </add> + <add> + Update the internal fork of Apache Commons DBCP to 6d232e5 (2020-08-11, + 2.8.0-SNAPSHOT). Code clean-up various bug fixes. (markt) + </add> </changelog> </subsection> </section> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org