This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-dbcp.git
The following commit(s) were added to refs/heads/master by this push: new 05cf6c3 DBCP-566: clearStatementPoolOnReturn (#42) 05cf6c3 is described below commit 05cf6c32b2b82f21cfca6239933c8670649f08d8 Author: Robert Paschek <os...@gmx.at> AuthorDate: Fri Jul 31 21:08:22 2020 +0200 DBCP-566: clearStatementPoolOnReturn (#42) * DBCP-566: clearStatementPoolOnReturn * Removed redundant assert inner2 is the same as inner1 * Javadoc updates * New method isClearStatementPoolOnReturn needs to have a default implementation to retain binary compatibility * Getter for statement pool * Fixed spelling errors and missing Javadoc Co-authored-by: Robert Paschek <robert.pasc...@comm-unity.at> --- .../org/apache/commons/dbcp2/BasicDataSource.java | 25 +++++++++ .../commons/dbcp2/BasicDataSourceFactory.java | 7 +++ .../commons/dbcp2/BasicDataSourceMXBean.java | 10 ++++ .../apache/commons/dbcp2/PoolableConnection.java | 3 ++ .../commons/dbcp2/PoolableConnectionFactory.java | 14 +++++ .../apache/commons/dbcp2/PoolingConnection.java | 40 ++++++++++++++ .../dbcp2/managed/BasicManagedDataSource.java | 1 + .../commons/dbcp2/TestBasicDataSourceFactory.java | 2 + .../commons/dbcp2/TestBasicDataSourceMXBean.java | 5 ++ .../dbcp2/TestPStmtPoolingBasicDataSource.java | 62 ++++++++++++++++++++++ 10 files changed, 169 insertions(+) diff --git a/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java b/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java index 337c40d..cb96d46 100644 --- a/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java +++ b/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java @@ -221,6 +221,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 @@ -655,6 +657,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()); @@ -1509,6 +1512,17 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean return this.poolPreparedStatements; } + /** + * 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; + } + @Override public boolean isWrapperFor(final Class<?> iface) throws SQLException { return false; @@ -2219,6 +2233,17 @@ 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; + } + + /** * @param removeAbandonedOnBorrow true means abandoned connections may be removed when connections are borrowed from * the pool. * @see #getRemoveAbandonedOnBorrow() diff --git a/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java b/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java index 37a2ffe..d7bb1bd 100644 --- a/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java +++ b/src/main/java/org/apache/commons/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, @@ -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/src/main/java/org/apache/commons/dbcp2/BasicDataSourceMXBean.java b/src/main/java/org/apache/commons/dbcp2/BasicDataSourceMXBean.java index 4754764..85f8a58 100644 --- a/src/main/java/org/apache/commons/dbcp2/BasicDataSourceMXBean.java +++ b/src/main/java/org/apache/commons/dbcp2/BasicDataSourceMXBean.java @@ -132,6 +132,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()} diff --git a/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java b/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java index 6a6ccf4..4365a21 100644 --- a/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java +++ b/src/main/java/org/apache/commons/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/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java b/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java index 50f4ab5..c744f49 100644 --- a/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java +++ b/src/main/java/org/apache/commons/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/src/main/java/org/apache/commons/dbcp2/PoolingConnection.java b/src/main/java/org/apache/commons/dbcp2/PoolingConnection.java index d99ed22..8237dc7 100644 --- a/src/main/java/org/apache/commons/dbcp2/PoolingConnection.java +++ b/src/main/java/org/apache/commons/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. * @@ -578,6 +580,27 @@ public class PoolingConnection extends DelegatingConnection<Connection> pstmtPool = pool; } + /** + * Returns the prepared statement pool we're using. + * + * @return statement pool + * @since 2.8.0 + */ + public KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> getStatementPool() { + return pstmtPool; + } + + /** + * 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; + } + @Override public synchronized String toString() { if (pstmtPool != null) { @@ -599,4 +622,21 @@ public class PoolingConnection extends DelegatingConnection<Connection> public boolean validateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject) { return true; } + + /** + * 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); + } + } + } } diff --git a/src/main/java/org/apache/commons/dbcp2/managed/BasicManagedDataSource.java b/src/main/java/org/apache/commons/dbcp2/managed/BasicManagedDataSource.java index 11dcbcd..0a3dae6 100644 --- a/src/main/java/org/apache/commons/dbcp2/managed/BasicManagedDataSource.java +++ b/src/main/java/org/apache/commons/dbcp2/managed/BasicManagedDataSource.java @@ -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/src/test/java/org/apache/commons/dbcp2/TestBasicDataSourceFactory.java b/src/test/java/org/apache/commons/dbcp2/TestBasicDataSourceFactory.java index 5ba12fd..83dcbea 100644 --- a/src/test/java/org/apache/commons/dbcp2/TestBasicDataSourceFactory.java +++ b/src/test/java/org/apache/commons/dbcp2/TestBasicDataSourceFactory.java @@ -137,6 +137,7 @@ public class TestBasicDataSourceFactory { properties.setProperty("logAbandoned", "true"); properties.setProperty("abandonedUsageTracking", "true"); properties.setProperty("poolPreparedStatements", "true"); + properties.setProperty("clearStatementPoolOnReturn", "true"); properties.setProperty("maxOpenPreparedStatements", "10"); properties.setProperty("lifo", "true"); properties.setProperty("fastFailValidation", "true"); @@ -180,6 +181,7 @@ public class TestBasicDataSourceFactory { assertTrue(ds.getLogAbandoned()); assertTrue(ds.getAbandonedUsageTracking()); assertTrue(ds.isPoolPreparedStatements()); + assertTrue(ds.isClearStatementPoolOnReturn()); assertEquals(10, ds.getMaxOpenPreparedStatements()); assertTrue(ds.getLifo()); assertTrue(ds.getFastFailValidation()); diff --git a/src/test/java/org/apache/commons/dbcp2/TestBasicDataSourceMXBean.java b/src/test/java/org/apache/commons/dbcp2/TestBasicDataSourceMXBean.java index 6576777..f0f3a38 100644 --- a/src/test/java/org/apache/commons/dbcp2/TestBasicDataSourceMXBean.java +++ b/src/test/java/org/apache/commons/dbcp2/TestBasicDataSourceMXBean.java @@ -42,6 +42,11 @@ public class TestBasicDataSourceMXBean { } @Override + public boolean isClearStatementPoolOnReturn() { + return false; + } + + @Override public boolean isClosed() { return false; } diff --git a/src/test/java/org/apache/commons/dbcp2/TestPStmtPoolingBasicDataSource.java b/src/test/java/org/apache/commons/dbcp2/TestPStmtPoolingBasicDataSource.java index 734ebcc..e2d7e20 100644 --- a/src/test/java/org/apache/commons/dbcp2/TestPStmtPoolingBasicDataSource.java +++ b/src/test/java/org/apache/commons/dbcp2/TestPStmtPoolingBasicDataSource.java @@ -29,6 +29,7 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; +import org.apache.commons.pool2.KeyedObjectPool; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -234,6 +235,67 @@ public class TestPStmtPoolingBasicDataSource extends TestBasicDataSource { } /** + * Tests clearStatementPoolOnReturn introduced with DBCP-566. + * When turned on, the statement pool must be empty after the connection is closed. + * + * @throws Exception + * @since 2.8.0 + */ + @Test + public void testPStmtPoolingAcrossCloseWithClearOnReturn() throws Exception { + ds.setMaxTotal(1); // only one connection in pool needed + ds.setMaxIdle(1); + ds.setClearStatementPoolOnReturn(true); + ds.setAccessToUnderlyingConnectionAllowed(true); + final Connection conn1 = getConnection(); + assertNotNull(conn1); + assertEquals(1, ds.getNumActive()); + assertEquals(0, ds.getNumIdle()); + + @SuppressWarnings("unchecked") + final DelegatingConnection<Connection> poolableConn = + (DelegatingConnection<Connection>) ((DelegatingConnection<Connection>) conn1).getDelegateInternal(); + final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool = + ((PoolingConnection) poolableConn.getDelegateInternal()).getStatementPool(); + + final PreparedStatement stmt1 = conn1.prepareStatement("select 'a' from dual"); + assertNotNull(stmt1); + final Statement inner1 = ((DelegatingPreparedStatement) stmt1).getInnermostDelegate(); + assertNotNull(inner1); + stmt1.close(); + + final PreparedStatement stmt2 = conn1.prepareStatement("select 'a' from dual"); + assertNotNull(stmt2); + final Statement inner2 = ((DelegatingPreparedStatement) stmt2).getInnermostDelegate(); + assertNotNull(inner2); + assertSame(inner1, inner2); // from the same connection the statement must be pooled + stmt2.close(); + + conn1.close(); + assertTrue(inner1.isClosed()); + + assertEquals(0, stmtPool.getNumActive()); + assertEquals(0, stmtPool.getNumIdle()); + + assertEquals(0, ds.getNumActive()); + assertEquals(1, ds.getNumIdle()); + + final Connection conn2 = getConnection(); + assertNotNull(conn2); + assertEquals(1, ds.getNumActive()); + assertEquals(0, ds.getNumIdle()); + + final PreparedStatement stmt3 = conn2.prepareStatement("select 'a' from dual"); + assertNotNull(stmt3); + final Statement inner3 = ((DelegatingPreparedStatement) stmt3).getInnermostDelegate(); + assertNotNull(inner3); + + assertNotSame(inner1, inner3); // when acquiring the connection the next time, statement must be new + + conn2.close(); + } + + /** * Tests high-concurrency contention for connections and pooled prepared statements. * DBCP-414 */