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.paschek@comm-unity.at>
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 @@
*/
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 @@
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 @@
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 @@
}
/**
+ * 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
private boolean poolStatements;
+ private boolean clearStatementPoolOnReturn;
+
private int maxOpenPreparedStatements = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY;
private long maxConnLifetimeMillis = -1;
@@ -392,6 +394,7 @@
final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool = new GenericKeyedObjectPool<>(
poolingConn, config);
poolingConn.setStatementPool(stmtPool);
+ poolingConn.setClearStatementPoolOnReturn(clearStatementPoolOnReturn);
poolingConn.setCacheState(cacheState);
}
@@ -597,6 +600,17 @@
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 @@
/** Pool of {@link PreparedStatement}s. and {@link CallableStatement}s */
private KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> pstmtPool;
+ private boolean clearStatementPoolOnReturn = false;
+
/**
* Constructor.
*
@@ -578,6 +580,27 @@
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 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 @@
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 @@
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 @@
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 @@
}
@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.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 @@
}
/**
+ * 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
*/