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
      */

Reply via email to