You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2022/07/10 18:42:19 UTC

[commons-dbcp] branch master updated: Use forEach and make AbandonedTrace processing type-safe

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 e92a0374 Use forEach and make AbandonedTrace processing type-safe
e92a0374 is described below

commit e92a0374aff8f973cf29cb3f103224ddb87b127d
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sun Jul 10 14:42:12 2022 -0400

    Use forEach and make AbandonedTrace processing type-safe
---
 .../org/apache/commons/dbcp2/BasicDataSource.java  |   25 +-
 .../commons/dbcp2/BasicDataSourceFactory.java      |   21 +-
 .../apache/commons/dbcp2/DelegatingConnection.java |   27 +-
 .../commons/dbcp2/DelegatingPreparedStatement.java |   13 +-
 .../apache/commons/dbcp2/DelegatingStatement.java  | 1651 ++++++++++----------
 .../commons/dbcp2/PoolableConnectionFactory.java   |    5 +-
 .../dbcp2/cpdsadapter/PooledConnectionImpl.java    |    5 +-
 .../datasources/InstanceKeyDataSourceFactory.java  |   10 +-
 .../dbcp2/datasources/PerUserPoolDataSource.java   |    9 +-
 9 files changed, 870 insertions(+), 896 deletions(-)

diff --git a/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java b/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java
index 0ac82e28..23dd2bb3 100644
--- a/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java
+++ b/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java
@@ -38,6 +38,7 @@ import java.util.Properties;
 import java.util.Set;
 import java.util.function.BiConsumer;
 import java.util.logging.Logger;
+import java.util.stream.Stream;
 
 import javax.management.MBeanRegistration;
 import javax.management.MBeanServer;
@@ -1822,20 +1823,18 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
         Objects.requireNonNull(connectionProperties, "connectionProperties");
         final String[] entries = connectionProperties.split(";");
         final Properties properties = new Properties();
-        for (final String entry : entries) {
-            if (!entry.isEmpty()) {
-                final int index = entry.indexOf('=');
-                if (index > 0) {
-                    final String name = entry.substring(0, index);
-                    final String value = entry.substring(index + 1);
-                    properties.setProperty(name, value);
-                } else {
-                    // no value is empty string which is how
-                    // java.util.Properties works
-                    properties.setProperty(entry, "");
-                }
+        Stream.of(entries).filter(e -> !e.isEmpty()).forEach(entry -> {
+            final int index = entry.indexOf('=');
+            if (index > 0) {
+                final String name = entry.substring(0, index);
+                final String value = entry.substring(index + 1);
+                properties.setProperty(name, value);
+            } else {
+                // no value is empty string which is how
+                // java.util.Properties works
+                properties.setProperty(entry, "");
             }
-        }
+        });
         this.connectionProperties = properties;
     }
 
diff --git a/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java b/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java
index 457bc9e0..f5a73c89 100644
--- a/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java
+++ b/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java
@@ -31,7 +31,6 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Properties;
@@ -139,7 +138,7 @@ public class BasicDataSourceFactory implements ObjectFactory {
     private static final String SILENT_PROP_SINGLETON = "singleton";
     private static final String SILENT_PROP_AUTH = "auth";
 
-    private static final String[] ALL_PROPERTIES = {PROP_DEFAULT_AUTO_COMMIT, PROP_DEFAULT_READ_ONLY,
+    private static final List<String> ALL_PROPERTY_NAMES = Arrays.asList(PROP_DEFAULT_AUTO_COMMIT, PROP_DEFAULT_READ_ONLY,
             PROP_DEFAULT_TRANSACTION_ISOLATION, PROP_DEFAULT_CATALOG, PROP_DEFAULT_SCHEMA, PROP_CACHE_STATE,
             PROP_DRIVER_CLASS_NAME, PROP_LIFO, PROP_MAX_TOTAL, PROP_MAX_IDLE, PROP_MIN_IDLE, PROP_INITIAL_SIZE,
             PROP_MAX_WAIT_MILLIS, PROP_TEST_ON_CREATE, PROP_TEST_ON_BORROW, PROP_TEST_ON_RETURN,
@@ -152,7 +151,7 @@ public class BasicDataSourceFactory implements ObjectFactory {
             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,
-            PROP_REGISTER_CONNECTION_MBEAN, PROP_CONNECTION_FACTORY_CLASS_NAME};
+            PROP_REGISTER_CONNECTION_MBEAN, PROP_CONNECTION_FACTORY_CLASS_NAME);
 
     /**
      * Obsolete properties from DBCP 1.x. with warning strings suggesting new properties. LinkedHashMap will guarantee
@@ -397,12 +396,12 @@ public class BasicDataSourceFactory implements ObjectFactory {
         infoMessages.forEach(log::info);
 
         final Properties properties = new Properties();
-        for (final String propertyName : ALL_PROPERTIES) {
+        ALL_PROPERTY_NAMES.forEach(propertyName -> {
             final RefAddr ra = ref.get(propertyName);
             if (ra != null) {
                 properties.setProperty(propertyName, Objects.toString(ra.getContent(), null));
             }
-        }
+        });
 
         return createDataSource(properties);
     }
@@ -422,20 +421,18 @@ public class BasicDataSourceFactory implements ObjectFactory {
      */
     private void validatePropertyNames(final Reference ref, final Name name, final List<String> warnMessages,
         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.isEmpty()) {
-            for (final Entry<String, String> entry : NUPROP_WARNTEXT.entrySet()) {
-                final String propertyName = entry.getKey();
+            NUPROP_WARNTEXT.forEach((propertyName, value) -> {
                 final RefAddr ra = ref.get(propertyName);
-                if (ra != null && !allPropsAsList.contains(ra.getType())) {
+                if (ra != null && !ALL_PROPERTY_NAMES.contains(ra.getType())) {
                     final StringBuilder stringBuilder = new StringBuilder(nameString);
                     final String propertyValue = Objects.toString(ra.getContent(), null);
-                    stringBuilder.append(entry.getValue()).append(" You have set value of \"").append(propertyValue).append("\" for \"").append(propertyName)
+                    stringBuilder.append(value).append(" You have set value of \"").append(propertyValue).append("\" for \"").append(propertyName)
                         .append("\" property, which is being ignored.");
                     warnMessages.add(stringBuilder.toString());
                 }
-            }
+            });
         }
 
         final Enumeration<RefAddr> allRefAddrs = ref.getAll();
@@ -444,7 +441,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.containsKey(propertyName) || SILENT_PROPERTIES.contains(propertyName))) {
+            if (!(ALL_PROPERTY_NAMES.contains(propertyName) || NUPROP_WARNTEXT.containsKey(propertyName) || SILENT_PROPERTIES.contains(propertyName))) {
                 final String propertyValue = Objects.toString(ra.getContent(), null);
                 final StringBuilder stringBuilder = new StringBuilder(nameString);
                 stringBuilder.append("Ignoring unknown property: ").append("value of \"").append(propertyValue).append("\" for \"").append(propertyName)
diff --git a/src/main/java/org/apache/commons/dbcp2/DelegatingConnection.java b/src/main/java/org/apache/commons/dbcp2/DelegatingConnection.java
index 62163a03..66c8481f 100644
--- a/src/main/java/org/apache/commons/dbcp2/DelegatingConnection.java
+++ b/src/main/java/org/apache/commons/dbcp2/DelegatingConnection.java
@@ -25,7 +25,6 @@ import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.NClob;
 import java.sql.PreparedStatement;
-import java.sql.ResultSet;
 import java.sql.SQLClientInfoException;
 import java.sql.SQLException;
 import java.sql.SQLWarning;
@@ -651,26 +650,16 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
         // The JDBC specification requires that a Connection close any open
         // Statement's when it is closed.
         // DBCP-288. Not all the traced objects will be statements
-        final List<AbandonedTrace> traces = getTrace();
-        if (traces != null && !traces.isEmpty()) {
+        final List<AbandonedTrace> traceList = getTrace();
+        if (traceList != null && !traceList.isEmpty()) {
             final List<Exception> thrownList = new ArrayList<>();
-            for (final Object trace : traces) {
-                if (trace instanceof Statement) {
-                    try {
-                        ((Statement) trace).close();
-                    } catch (final Exception e) {
-                        thrownList.add(e);
-                    }
-                } else if (trace instanceof ResultSet) {
-                    // DBCP-265: Need to close the result sets that are
-                    // generated via DatabaseMetaData
-                    try {
-                        ((ResultSet) trace).close();
-                    } catch (final Exception e) {
-                        thrownList.add(e);
-                    }
+            traceList.stream().filter(AutoCloseable.class::isInstance).forEach(trace -> {
+                try {
+                    ((AutoCloseable) trace).close();
+                } catch (final Exception e) {
+                    thrownList.add(e);
                 }
-            }
+            });
             clearTrace();
             if (!thrownList.isEmpty()) {
                 throw new SQLExceptionList(thrownList);
diff --git a/src/main/java/org/apache/commons/dbcp2/DelegatingPreparedStatement.java b/src/main/java/org/apache/commons/dbcp2/DelegatingPreparedStatement.java
index 4f343b39..78e868b0 100644
--- a/src/main/java/org/apache/commons/dbcp2/DelegatingPreparedStatement.java
+++ b/src/main/java/org/apache/commons/dbcp2/DelegatingPreparedStatement.java
@@ -697,7 +697,7 @@ public class DelegatingPreparedStatement extends DelegatingStatement implements
     protected void prepareToReturn() throws SQLException {
         setClosedInternal(true);
         removeThisTrace(getConnectionInternal());
-    
+
         // The JDBC spec requires that a statement close any open
         // ResultSet's when it is closed.
         // FIXME The PreparedStatement we're wrapping should handle this for us.
@@ -705,22 +705,21 @@ public class DelegatingPreparedStatement extends DelegatingStatement implements
         final List<AbandonedTrace> resultSetList = getTrace();
         if (resultSetList != null) {
             final List<Exception> thrownList = new ArrayList<>();
-            final ResultSet[] resultSets = resultSetList.toArray(Utils.EMPTY_RESULT_SET_ARRAY);
-            for (final ResultSet resultSet : resultSets) {
-                if (resultSet != null) {
+            resultSetList.forEach(trace -> {
+                if (trace instanceof AutoCloseable) {
                     try {
-                        resultSet.close();
+                        ((AutoCloseable) trace).close();
                     } catch (final Exception e) {
                         thrownList.add(e);
                     }
                 }
-            }
+            });
             clearTrace();
             if (!thrownList.isEmpty()) {
                 throw new SQLExceptionList(thrownList);
             }
         }
-    
+
         super.passivate();
     }
 }
diff --git a/src/main/java/org/apache/commons/dbcp2/DelegatingStatement.java b/src/main/java/org/apache/commons/dbcp2/DelegatingStatement.java
index 0fd57837..cf05106a 100644
--- a/src/main/java/org/apache/commons/dbcp2/DelegatingStatement.java
+++ b/src/main/java/org/apache/commons/dbcp2/DelegatingStatement.java
@@ -1,827 +1,824 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.dbcp2;
-
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.SQLWarning;
-import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A base delegating implementation of {@link Statement}.
- * <p>
- * All of the methods from the {@link Statement} interface simply check to see that the {@link Statement} is active, and
- * call the corresponding method on the "delegate" provided in my constructor.
- * <p>
- * Extends AbandonedTrace to implement Statement tracking and logging of code which created the Statement. Tracking the
- * Statement ensures that the Connection which created it can close any open Statement's on Connection close.
- *
- * @since 2.0
- */
-public class DelegatingStatement extends AbandonedTrace implements Statement {
-
-    /** My delegate. */
-    private Statement statement;
-
-    /** The connection that created me. **/
-    private DelegatingConnection<?> connection;
-
-    private boolean closed;
-
-    /**
-     * Create a wrapper for the Statement which traces this Statement to the Connection which created it and the code
-     * which created it.
-     *
-     * @param statement
-     *            the {@link Statement} to delegate all calls to.
-     * @param connection
-     *            the {@link DelegatingConnection} that created this statement.
-     */
-    public DelegatingStatement(final DelegatingConnection<?> connection, final Statement statement) {
-        super(connection);
-        this.statement = statement;
-        this.connection = connection;
-    }
-
-    /**
-     *
-     * @throws SQLException
-     *             thrown by the delegating statement.
-     * @since 2.4.0 made public, was protected in 2.3.0.
-     */
-    public void activate() throws SQLException {
-        if (statement instanceof DelegatingStatement) {
-            ((DelegatingStatement) statement).activate();
-        }
-    }
-
-    @Override
-    public void addBatch(final String sql) throws SQLException {
-        checkOpen();
-        try {
-            statement.addBatch(sql);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    @Override
-    public void cancel() throws SQLException {
-        checkOpen();
-        try {
-            statement.cancel();
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    protected void checkOpen() throws SQLException {
-        if (isClosed()) {
-            throw new SQLException(this.getClass().getName() + " with address: \"" + this.toString() + "\" is closed.");
-        }
-    }
-
-    @Override
-    public void clearBatch() throws SQLException {
-        checkOpen();
-        try {
-            statement.clearBatch();
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    @Override
-    public void clearWarnings() throws SQLException {
-        checkOpen();
-        try {
-            statement.clearWarnings();
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    /**
-     * Close this DelegatingStatement, and close any ResultSets that were not explicitly closed.
-     */
-    @Override
-    public void close() throws SQLException {
-        if (isClosed()) {
-            return;
-        }
-        final List<Exception> thrownList = new ArrayList<>();
-        try {
-            if (connection != null) {
-                connection.removeTrace(this);
-                connection = null;
-            }
-
-            // The JDBC spec requires that a statement close any open
-            // ResultSet's when it is closed.
-            // FIXME The PreparedStatement we're wrapping should handle this for us.
-            // See bug 17301 for what could happen when ResultSets are closed twice.
-            final List<AbandonedTrace> resultSetList = getTrace();
-            if (resultSetList != null) {
-                final ResultSet[] resultSets = resultSetList.toArray(Utils.EMPTY_RESULT_SET_ARRAY);
-                for (final ResultSet resultSet : resultSets) {
-                    if (resultSet != null) {
-                        try {
-                            resultSet.close();
-                        } catch (final Exception e) {
-                            if (connection != null) {
-                                // Does not rethrow e.
-                                connection.handleExceptionNoThrow(e);
-                            }
-                            thrownList.add(e);
-                        }
-                    }
-                }
-                clearTrace();
-            }
-            if (statement != null) {
-                try {
-                    statement.close();
-                } catch (final Exception e) {
-                    if (connection != null) {
-                        // Does not rethrow e.
-                        connection.handleExceptionNoThrow(e);
-                    }
-                    thrownList.add(e);
-                }
-            }
-        } finally {
-            closed = true;
-            statement = null;
-            if (!thrownList.isEmpty()) {
-                throw new SQLExceptionList(thrownList);
-            }
-        }
-    }
-
-    @Override
-    public void closeOnCompletion() throws SQLException {
-        checkOpen();
-        try {
-            Jdbc41Bridge.closeOnCompletion(statement);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    @Override
-    public boolean execute(final String sql) throws SQLException {
-        checkOpen();
-        setLastUsedInParent();
-        try {
-            return statement.execute(sql);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
-    }
-
-    @Override
-    public boolean execute(final String sql, final int autoGeneratedKeys) throws SQLException {
-        checkOpen();
-        setLastUsedInParent();
-        try {
-            return statement.execute(sql, autoGeneratedKeys);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
-    }
-
-    @Override
-    public boolean execute(final String sql, final int[] columnIndexes) throws SQLException {
-        checkOpen();
-        setLastUsedInParent();
-        try {
-            return statement.execute(sql, columnIndexes);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
-    }
-
-    @Override
-    public boolean execute(final String sql, final String[] columnNames) throws SQLException {
-        checkOpen();
-        setLastUsedInParent();
-        try {
-            return statement.execute(sql, columnNames);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
-    }
-
-    @Override
-    public int[] executeBatch() throws SQLException {
-        checkOpen();
-        setLastUsedInParent();
-        try {
-            return statement.executeBatch();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
-    }
-
-    /**
-     * @since 2.5.0
-     */
-    @Override
-    public long[] executeLargeBatch() throws SQLException {
-        checkOpen();
-        setLastUsedInParent();
-        try {
-            return statement.executeLargeBatch();
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    /**
-     * @since 2.5.0
-     */
-    @Override
-    public long executeLargeUpdate(final String sql) throws SQLException {
-        checkOpen();
-        setLastUsedInParent();
-        try {
-            return statement.executeLargeUpdate(sql);
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    /**
-     * @since 2.5.0
-     */
-    @Override
-    public long executeLargeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException {
-        checkOpen();
-        setLastUsedInParent();
-        try {
-            return statement.executeLargeUpdate(sql, autoGeneratedKeys);
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    /**
-     * @since 2.5.0
-     */
-    @Override
-    public long executeLargeUpdate(final String sql, final int[] columnIndexes) throws SQLException {
-        checkOpen();
-        setLastUsedInParent();
-        try {
-            return statement.executeLargeUpdate(sql, columnIndexes);
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    /**
-     * @since 2.5.0
-     */
-    @Override
-    public long executeLargeUpdate(final String sql, final String[] columnNames) throws SQLException {
-        checkOpen();
-        setLastUsedInParent();
-        try {
-            return statement.executeLargeUpdate(sql, columnNames);
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
-    @Override
-    public ResultSet executeQuery(final String sql) throws SQLException {
-        checkOpen();
-        setLastUsedInParent();
-        try {
-            return DelegatingResultSet.wrapResultSet(this, statement.executeQuery(sql));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
-    }
-
-    @Override
-    public int executeUpdate(final String sql) throws SQLException {
-        checkOpen();
-        setLastUsedInParent();
-        try {
-            return statement.executeUpdate(sql);
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    @Override
-    public int executeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException {
-        checkOpen();
-        setLastUsedInParent();
-        try {
-            return statement.executeUpdate(sql, autoGeneratedKeys);
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    @Override
-    public int executeUpdate(final String sql, final int[] columnIndexes) throws SQLException {
-        checkOpen();
-        setLastUsedInParent();
-        try {
-            return statement.executeUpdate(sql, columnIndexes);
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    @Override
-    public int executeUpdate(final String sql, final String[] columnNames) throws SQLException {
-        checkOpen();
-        setLastUsedInParent();
-        try {
-            return statement.executeUpdate(sql, columnNames);
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        // This is required because of statement pooling. The poolable
-        // statements will always be strongly held by the statement pool. If the
-        // delegating statements that wrap the poolable statement are not
-        // strongly held they will be garbage collected but at that point the
-        // poolable statements need to be returned to the pool else there will
-        // be a leak of statements from the pool. Closing this statement will
-        // close all the wrapped statements and return any poolable statements
-        // to the pool.
-        close();
-        super.finalize();
-    }
-
-    @Override
-    public Connection getConnection() throws SQLException {
-        checkOpen();
-        return getConnectionInternal(); // return the delegating connection that created this
-    }
-
-    protected DelegatingConnection<?> getConnectionInternal() {
-        return connection;
-    }
-
-    /**
-     * Returns my underlying {@link Statement}.
-     *
-     * @return my underlying {@link Statement}.
-     * @see #getInnermostDelegate
-     */
-    public Statement getDelegate() {
-        return statement;
-    }
-
-    @Override
-    public int getFetchDirection() throws SQLException {
-        checkOpen();
-        try {
-            return statement.getFetchDirection();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    @Override
-    public int getFetchSize() throws SQLException {
-        checkOpen();
-        try {
-            return statement.getFetchSize();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
-    @Override
-    public ResultSet getGeneratedKeys() throws SQLException {
-        checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(this, statement.getGeneratedKeys());
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
-    }
-
-    /**
-     * If my underlying {@link Statement} is not a {@code DelegatingStatement}, returns it, otherwise recursively
-     * invokes this method on my delegate.
-     * <p>
-     * Hence this method will return the first delegate that is not a {@code DelegatingStatement} or {@code null} when
-     * no non-{@code DelegatingStatement} delegate can be found by traversing this chain.
-     * </p>
-     * <p>
-     * This method is useful when you may have nested {@code DelegatingStatement}s, and you want to make sure to obtain
-     * a "genuine" {@link Statement}.
-     * </p>
-     *
-     * @return The innermost delegate.
-     *
-     * @see #getDelegate
-     */
-    @SuppressWarnings("resource")
-    public Statement getInnermostDelegate() {
-        Statement s = statement;
-        while (s instanceof DelegatingStatement) {
-            s = ((DelegatingStatement) s).getDelegate();
-            if (this == s) {
-                return null;
-            }
-        }
-        return s;
-    }
-
-    /**
-     * @since 2.5.0
-     */
-    @Override
-    public long getLargeMaxRows() throws SQLException {
-        checkOpen();
-        try {
-            return statement.getLargeMaxRows();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    /**
-     * @since 2.5.0
-     */
-    @Override
-    public long getLargeUpdateCount() throws SQLException {
-        checkOpen();
-        try {
-            return statement.getLargeUpdateCount();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    @Override
-    public int getMaxFieldSize() throws SQLException {
-        checkOpen();
-        try {
-            return statement.getMaxFieldSize();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    @Override
-    public int getMaxRows() throws SQLException {
-        checkOpen();
-        try {
-            return statement.getMaxRows();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    @Override
-    public boolean getMoreResults() throws SQLException {
-        checkOpen();
-        try {
-            return statement.getMoreResults();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
-    }
-
-    @Override
-    public boolean getMoreResults(final int current) throws SQLException {
-        checkOpen();
-        try {
-            return statement.getMoreResults(current);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
-    }
-
-    @Override
-    public int getQueryTimeout() throws SQLException {
-        checkOpen();
-        try {
-            return statement.getQueryTimeout();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
-    @Override
-    public ResultSet getResultSet() throws SQLException {
-        checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(this, statement.getResultSet());
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
-    }
-
-    @Override
-    public int getResultSetConcurrency() throws SQLException {
-        checkOpen();
-        try {
-            return statement.getResultSetConcurrency();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    @Override
-    public int getResultSetHoldability() throws SQLException {
-        checkOpen();
-        try {
-            return statement.getResultSetHoldability();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    @Override
-    public int getResultSetType() throws SQLException {
-        checkOpen();
-        try {
-            return statement.getResultSetType();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    @Override
-    public int getUpdateCount() throws SQLException {
-        checkOpen();
-        try {
-            return statement.getUpdateCount();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    @Override
-    public SQLWarning getWarnings() throws SQLException {
-        checkOpen();
-        try {
-            return statement.getWarnings();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
-    }
-
-    protected void handleException(final SQLException e) throws SQLException {
-        if (connection == null) {
-            throw e;
-        }
-        connection.handleException(e);
-    }
-
-    /*
-     * Note: This method was protected prior to JDBC 4.
-     */
-    @Override
-    public boolean isClosed() throws SQLException {
-        return closed;
-    }
-
-    protected boolean isClosedInternal() {
-        return closed;
-    }
-
-    @Override
-    public boolean isCloseOnCompletion() throws SQLException {
-        checkOpen();
-        try {
-            return Jdbc41Bridge.isCloseOnCompletion(statement);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
-    }
-
-    @Override
-    public boolean isPoolable() throws SQLException {
-        checkOpen();
-        try {
-            return statement.isPoolable();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
-    }
-
-    @Override
-    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
-        if (iface.isAssignableFrom(getClass())) {
-            return true;
-        }
-        if (iface.isAssignableFrom(statement.getClass())) {
-            return true;
-        }
-        return statement.isWrapperFor(iface);
-    }
-
-    /**
-     *
-     * @throws SQLException
-     *             thrown by the delegating statement.
-     * @since 2.4.0 made public, was protected in 2.3.0.
-     */
-    public void passivate() throws SQLException {
-        if (statement instanceof DelegatingStatement) {
-            ((DelegatingStatement) statement).passivate();
-        }
-    }
-
-    protected void setClosedInternal(final boolean closed) {
-        this.closed = closed;
-    }
-
-    @Override
-    public void setCursorName(final String name) throws SQLException {
-        checkOpen();
-        try {
-            statement.setCursorName(name);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    /**
-     * Sets my delegate.
-     *
-     * @param statement
-     *            my delegate.
-     */
-    public void setDelegate(final Statement statement) {
-        this.statement = statement;
-    }
-
-    @Override
-    public void setEscapeProcessing(final boolean enable) throws SQLException {
-        checkOpen();
-        try {
-            statement.setEscapeProcessing(enable);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    @Override
-    public void setFetchDirection(final int direction) throws SQLException {
-        checkOpen();
-        try {
-            statement.setFetchDirection(direction);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    @Override
-    public void setFetchSize(final int rows) throws SQLException {
-        checkOpen();
-        try {
-            statement.setFetchSize(rows);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    /**
-     * @since 2.5.0
-     */
-    @Override
-    public void setLargeMaxRows(final long max) throws SQLException {
-        checkOpen();
-        try {
-            statement.setLargeMaxRows(max);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    private void setLastUsedInParent() {
-        if (connection != null) {
-            connection.setLastUsed();
-        }
-    }
-
-    @Override
-    public void setMaxFieldSize(final int max) throws SQLException {
-        checkOpen();
-        try {
-            statement.setMaxFieldSize(max);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    @Override
-    public void setMaxRows(final int max) throws SQLException {
-        checkOpen();
-        try {
-            statement.setMaxRows(max);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    @Override
-    public void setPoolable(final boolean poolable) throws SQLException {
-        checkOpen();
-        try {
-            statement.setPoolable(poolable);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    @Override
-    public void setQueryTimeout(final int seconds) throws SQLException {
-        checkOpen();
-        try {
-            statement.setQueryTimeout(seconds);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    /**
-     * Returns a String representation of this object.
-     *
-     * @return String
-     */
-    @Override
-    public synchronized String toString() {
-        return statement == null ? "NULL" : statement.toString();
-    }
-
-    @Override
-    public <T> T unwrap(final Class<T> iface) throws SQLException {
-        if (iface.isAssignableFrom(getClass())) {
-            return iface.cast(this);
-        }
-        if (iface.isAssignableFrom(statement.getClass())) {
-            return iface.cast(statement);
-        }
-        return statement.unwrap(iface);
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.dbcp2;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A base delegating implementation of {@link Statement}.
+ * <p>
+ * All of the methods from the {@link Statement} interface simply check to see that the {@link Statement} is active, and
+ * call the corresponding method on the "delegate" provided in my constructor.
+ * <p>
+ * Extends AbandonedTrace to implement Statement tracking and logging of code which created the Statement. Tracking the
+ * Statement ensures that the Connection which created it can close any open Statement's on Connection close.
+ *
+ * @since 2.0
+ */
+public class DelegatingStatement extends AbandonedTrace implements Statement {
+
+    /** My delegate. */
+    private Statement statement;
+
+    /** The connection that created me. **/
+    private DelegatingConnection<?> connection;
+
+    private boolean closed;
+
+    /**
+     * Create a wrapper for the Statement which traces this Statement to the Connection which created it and the code
+     * which created it.
+     *
+     * @param statement
+     *            the {@link Statement} to delegate all calls to.
+     * @param connection
+     *            the {@link DelegatingConnection} that created this statement.
+     */
+    public DelegatingStatement(final DelegatingConnection<?> connection, final Statement statement) {
+        super(connection);
+        this.statement = statement;
+        this.connection = connection;
+    }
+
+    /**
+     *
+     * @throws SQLException
+     *             thrown by the delegating statement.
+     * @since 2.4.0 made public, was protected in 2.3.0.
+     */
+    public void activate() throws SQLException {
+        if (statement instanceof DelegatingStatement) {
+            ((DelegatingStatement) statement).activate();
+        }
+    }
+
+    @Override
+    public void addBatch(final String sql) throws SQLException {
+        checkOpen();
+        try {
+            statement.addBatch(sql);
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    @Override
+    public void cancel() throws SQLException {
+        checkOpen();
+        try {
+            statement.cancel();
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    protected void checkOpen() throws SQLException {
+        if (isClosed()) {
+            throw new SQLException(this.getClass().getName() + " with address: \"" + this.toString() + "\" is closed.");
+        }
+    }
+
+    @Override
+    public void clearBatch() throws SQLException {
+        checkOpen();
+        try {
+            statement.clearBatch();
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    @Override
+    public void clearWarnings() throws SQLException {
+        checkOpen();
+        try {
+            statement.clearWarnings();
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    /**
+     * Close this DelegatingStatement, and close any ResultSets that were not explicitly closed.
+     */
+    @Override
+    public void close() throws SQLException {
+        if (isClosed()) {
+            return;
+        }
+        final List<Exception> thrownList = new ArrayList<>();
+        try {
+            if (connection != null) {
+                connection.removeTrace(this);
+                connection = null;
+            }
+
+            // The JDBC spec requires that a statement close any open
+            // ResultSet's when it is closed.
+            // FIXME The PreparedStatement we're wrapping should handle this for us.
+            // See bug 17301 for what could happen when ResultSets are closed twice.
+            final List<AbandonedTrace> traceList = getTrace();
+            if (traceList != null) {
+                traceList.stream().filter(AutoCloseable.class::isInstance).forEach(trace -> {
+                    try {
+                        ((AutoCloseable) trace).close();
+                    } catch (final Exception e) {
+                        if (connection != null) {
+                            // Does not rethrow e.
+                            connection.handleExceptionNoThrow(e);
+                        }
+                        thrownList.add(e);
+                    }
+                });
+                clearTrace();
+            }
+            if (statement != null) {
+                try {
+                    statement.close();
+                } catch (final Exception e) {
+                    if (connection != null) {
+                        // Does not rethrow e.
+                        connection.handleExceptionNoThrow(e);
+                    }
+                    thrownList.add(e);
+                }
+            }
+        } finally {
+            closed = true;
+            statement = null;
+            if (!thrownList.isEmpty()) {
+                throw new SQLExceptionList(thrownList);
+            }
+        }
+    }
+
+    @Override
+    public void closeOnCompletion() throws SQLException {
+        checkOpen();
+        try {
+            Jdbc41Bridge.closeOnCompletion(statement);
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    @Override
+    public boolean execute(final String sql) throws SQLException {
+        checkOpen();
+        setLastUsedInParent();
+        try {
+            return statement.execute(sql);
+        } catch (final SQLException e) {
+            handleException(e);
+            return false;
+        }
+    }
+
+    @Override
+    public boolean execute(final String sql, final int autoGeneratedKeys) throws SQLException {
+        checkOpen();
+        setLastUsedInParent();
+        try {
+            return statement.execute(sql, autoGeneratedKeys);
+        } catch (final SQLException e) {
+            handleException(e);
+            return false;
+        }
+    }
+
+    @Override
+    public boolean execute(final String sql, final int[] columnIndexes) throws SQLException {
+        checkOpen();
+        setLastUsedInParent();
+        try {
+            return statement.execute(sql, columnIndexes);
+        } catch (final SQLException e) {
+            handleException(e);
+            return false;
+        }
+    }
+
+    @Override
+    public boolean execute(final String sql, final String[] columnNames) throws SQLException {
+        checkOpen();
+        setLastUsedInParent();
+        try {
+            return statement.execute(sql, columnNames);
+        } catch (final SQLException e) {
+            handleException(e);
+            return false;
+        }
+    }
+
+    @Override
+    public int[] executeBatch() throws SQLException {
+        checkOpen();
+        setLastUsedInParent();
+        try {
+            return statement.executeBatch();
+        } catch (final SQLException e) {
+            handleException(e);
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * @since 2.5.0
+     */
+    @Override
+    public long[] executeLargeBatch() throws SQLException {
+        checkOpen();
+        setLastUsedInParent();
+        try {
+            return statement.executeLargeBatch();
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    /**
+     * @since 2.5.0
+     */
+    @Override
+    public long executeLargeUpdate(final String sql) throws SQLException {
+        checkOpen();
+        setLastUsedInParent();
+        try {
+            return statement.executeLargeUpdate(sql);
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    /**
+     * @since 2.5.0
+     */
+    @Override
+    public long executeLargeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException {
+        checkOpen();
+        setLastUsedInParent();
+        try {
+            return statement.executeLargeUpdate(sql, autoGeneratedKeys);
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    /**
+     * @since 2.5.0
+     */
+    @Override
+    public long executeLargeUpdate(final String sql, final int[] columnIndexes) throws SQLException {
+        checkOpen();
+        setLastUsedInParent();
+        try {
+            return statement.executeLargeUpdate(sql, columnIndexes);
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    /**
+     * @since 2.5.0
+     */
+    @Override
+    public long executeLargeUpdate(final String sql, final String[] columnNames) throws SQLException {
+        checkOpen();
+        setLastUsedInParent();
+        try {
+            return statement.executeLargeUpdate(sql, columnNames);
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
+    @Override
+    public ResultSet executeQuery(final String sql) throws SQLException {
+        checkOpen();
+        setLastUsedInParent();
+        try {
+            return DelegatingResultSet.wrapResultSet(this, statement.executeQuery(sql));
+        } catch (final SQLException e) {
+            handleException(e);
+            throw new AssertionError();
+        }
+    }
+
+    @Override
+    public int executeUpdate(final String sql) throws SQLException {
+        checkOpen();
+        setLastUsedInParent();
+        try {
+            return statement.executeUpdate(sql);
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    @Override
+    public int executeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException {
+        checkOpen();
+        setLastUsedInParent();
+        try {
+            return statement.executeUpdate(sql, autoGeneratedKeys);
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    @Override
+    public int executeUpdate(final String sql, final int[] columnIndexes) throws SQLException {
+        checkOpen();
+        setLastUsedInParent();
+        try {
+            return statement.executeUpdate(sql, columnIndexes);
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    @Override
+    public int executeUpdate(final String sql, final String[] columnNames) throws SQLException {
+        checkOpen();
+        setLastUsedInParent();
+        try {
+            return statement.executeUpdate(sql, columnNames);
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        // This is required because of statement pooling. The poolable
+        // statements will always be strongly held by the statement pool. If the
+        // delegating statements that wrap the poolable statement are not
+        // strongly held they will be garbage collected but at that point the
+        // poolable statements need to be returned to the pool else there will
+        // be a leak of statements from the pool. Closing this statement will
+        // close all the wrapped statements and return any poolable statements
+        // to the pool.
+        close();
+        super.finalize();
+    }
+
+    @Override
+    public Connection getConnection() throws SQLException {
+        checkOpen();
+        return getConnectionInternal(); // return the delegating connection that created this
+    }
+
+    protected DelegatingConnection<?> getConnectionInternal() {
+        return connection;
+    }
+
+    /**
+     * Returns my underlying {@link Statement}.
+     *
+     * @return my underlying {@link Statement}.
+     * @see #getInnermostDelegate
+     */
+    public Statement getDelegate() {
+        return statement;
+    }
+
+    @Override
+    public int getFetchDirection() throws SQLException {
+        checkOpen();
+        try {
+            return statement.getFetchDirection();
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    @Override
+    public int getFetchSize() throws SQLException {
+        checkOpen();
+        try {
+            return statement.getFetchSize();
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
+    @Override
+    public ResultSet getGeneratedKeys() throws SQLException {
+        checkOpen();
+        try {
+            return DelegatingResultSet.wrapResultSet(this, statement.getGeneratedKeys());
+        } catch (final SQLException e) {
+            handleException(e);
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * If my underlying {@link Statement} is not a {@code DelegatingStatement}, returns it, otherwise recursively
+     * invokes this method on my delegate.
+     * <p>
+     * Hence this method will return the first delegate that is not a {@code DelegatingStatement} or {@code null} when
+     * no non-{@code DelegatingStatement} delegate can be found by traversing this chain.
+     * </p>
+     * <p>
+     * This method is useful when you may have nested {@code DelegatingStatement}s, and you want to make sure to obtain
+     * a "genuine" {@link Statement}.
+     * </p>
+     *
+     * @return The innermost delegate.
+     *
+     * @see #getDelegate
+     */
+    @SuppressWarnings("resource")
+    public Statement getInnermostDelegate() {
+        Statement s = statement;
+        while (s instanceof DelegatingStatement) {
+            s = ((DelegatingStatement) s).getDelegate();
+            if (this == s) {
+                return null;
+            }
+        }
+        return s;
+    }
+
+    /**
+     * @since 2.5.0
+     */
+    @Override
+    public long getLargeMaxRows() throws SQLException {
+        checkOpen();
+        try {
+            return statement.getLargeMaxRows();
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    /**
+     * @since 2.5.0
+     */
+    @Override
+    public long getLargeUpdateCount() throws SQLException {
+        checkOpen();
+        try {
+            return statement.getLargeUpdateCount();
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    @Override
+    public int getMaxFieldSize() throws SQLException {
+        checkOpen();
+        try {
+            return statement.getMaxFieldSize();
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    @Override
+    public int getMaxRows() throws SQLException {
+        checkOpen();
+        try {
+            return statement.getMaxRows();
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    @Override
+    public boolean getMoreResults() throws SQLException {
+        checkOpen();
+        try {
+            return statement.getMoreResults();
+        } catch (final SQLException e) {
+            handleException(e);
+            return false;
+        }
+    }
+
+    @Override
+    public boolean getMoreResults(final int current) throws SQLException {
+        checkOpen();
+        try {
+            return statement.getMoreResults(current);
+        } catch (final SQLException e) {
+            handleException(e);
+            return false;
+        }
+    }
+
+    @Override
+    public int getQueryTimeout() throws SQLException {
+        checkOpen();
+        try {
+            return statement.getQueryTimeout();
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
+    @Override
+    public ResultSet getResultSet() throws SQLException {
+        checkOpen();
+        try {
+            return DelegatingResultSet.wrapResultSet(this, statement.getResultSet());
+        } catch (final SQLException e) {
+            handleException(e);
+            throw new AssertionError();
+        }
+    }
+
+    @Override
+    public int getResultSetConcurrency() throws SQLException {
+        checkOpen();
+        try {
+            return statement.getResultSetConcurrency();
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    @Override
+    public int getResultSetHoldability() throws SQLException {
+        checkOpen();
+        try {
+            return statement.getResultSetHoldability();
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    @Override
+    public int getResultSetType() throws SQLException {
+        checkOpen();
+        try {
+            return statement.getResultSetType();
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    @Override
+    public int getUpdateCount() throws SQLException {
+        checkOpen();
+        try {
+            return statement.getUpdateCount();
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    @Override
+    public SQLWarning getWarnings() throws SQLException {
+        checkOpen();
+        try {
+            return statement.getWarnings();
+        } catch (final SQLException e) {
+            handleException(e);
+            throw new AssertionError();
+        }
+    }
+
+    protected void handleException(final SQLException e) throws SQLException {
+        if (connection == null) {
+            throw e;
+        }
+        connection.handleException(e);
+    }
+
+    /*
+     * Note: This method was protected prior to JDBC 4.
+     */
+    @Override
+    public boolean isClosed() throws SQLException {
+        return closed;
+    }
+
+    protected boolean isClosedInternal() {
+        return closed;
+    }
+
+    @Override
+    public boolean isCloseOnCompletion() throws SQLException {
+        checkOpen();
+        try {
+            return Jdbc41Bridge.isCloseOnCompletion(statement);
+        } catch (final SQLException e) {
+            handleException(e);
+            return false;
+        }
+    }
+
+    @Override
+    public boolean isPoolable() throws SQLException {
+        checkOpen();
+        try {
+            return statement.isPoolable();
+        } catch (final SQLException e) {
+            handleException(e);
+            return false;
+        }
+    }
+
+    @Override
+    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
+        if (iface.isAssignableFrom(getClass())) {
+            return true;
+        }
+        if (iface.isAssignableFrom(statement.getClass())) {
+            return true;
+        }
+        return statement.isWrapperFor(iface);
+    }
+
+    /**
+     *
+     * @throws SQLException
+     *             thrown by the delegating statement.
+     * @since 2.4.0 made public, was protected in 2.3.0.
+     */
+    public void passivate() throws SQLException {
+        if (statement instanceof DelegatingStatement) {
+            ((DelegatingStatement) statement).passivate();
+        }
+    }
+
+    protected void setClosedInternal(final boolean closed) {
+        this.closed = closed;
+    }
+
+    @Override
+    public void setCursorName(final String name) throws SQLException {
+        checkOpen();
+        try {
+            statement.setCursorName(name);
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    /**
+     * Sets my delegate.
+     *
+     * @param statement
+     *            my delegate.
+     */
+    public void setDelegate(final Statement statement) {
+        this.statement = statement;
+    }
+
+    @Override
+    public void setEscapeProcessing(final boolean enable) throws SQLException {
+        checkOpen();
+        try {
+            statement.setEscapeProcessing(enable);
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    @Override
+    public void setFetchDirection(final int direction) throws SQLException {
+        checkOpen();
+        try {
+            statement.setFetchDirection(direction);
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    @Override
+    public void setFetchSize(final int rows) throws SQLException {
+        checkOpen();
+        try {
+            statement.setFetchSize(rows);
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    /**
+     * @since 2.5.0
+     */
+    @Override
+    public void setLargeMaxRows(final long max) throws SQLException {
+        checkOpen();
+        try {
+            statement.setLargeMaxRows(max);
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    private void setLastUsedInParent() {
+        if (connection != null) {
+            connection.setLastUsed();
+        }
+    }
+
+    @Override
+    public void setMaxFieldSize(final int max) throws SQLException {
+        checkOpen();
+        try {
+            statement.setMaxFieldSize(max);
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    @Override
+    public void setMaxRows(final int max) throws SQLException {
+        checkOpen();
+        try {
+            statement.setMaxRows(max);
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    @Override
+    public void setPoolable(final boolean poolable) throws SQLException {
+        checkOpen();
+        try {
+            statement.setPoolable(poolable);
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    @Override
+    public void setQueryTimeout(final int seconds) throws SQLException {
+        checkOpen();
+        try {
+            statement.setQueryTimeout(seconds);
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    /**
+     * Returns a String representation of this object.
+     *
+     * @return String
+     */
+    @Override
+    public synchronized String toString() {
+        return statement == null ? "NULL" : statement.toString();
+    }
+
+    @Override
+    public <T> T unwrap(final Class<T> iface) throws SQLException {
+        if (iface.isAssignableFrom(getClass())) {
+            return iface.cast(this);
+        }
+        if (iface.isAssignableFrom(statement.getClass())) {
+            return iface.cast(statement);
+        }
+        return statement.unwrap(iface);
+    }
+}
diff --git a/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java b/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java
index a79a9019..fbca26ea 100644
--- a/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java
+++ b/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java
@@ -364,10 +364,9 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
             throw new SQLException("initializeConnection: connection closed");
         }
         if (null != sqls) {
-            try (Statement stmt = conn.createStatement()) {
+            try (Statement statement = conn.createStatement()) {
                 for (final String sql : sqls) {
-                    Objects.requireNonNull(sql, "null connectionInitSqls element");
-                    stmt.execute(sql);
+                    statement.execute(Objects.requireNonNull(sql, "null connectionInitSqls element"));
                 }
             }
         }
diff --git a/src/main/java/org/apache/commons/dbcp2/cpdsadapter/PooledConnectionImpl.java b/src/main/java/org/apache/commons/dbcp2/cpdsadapter/PooledConnectionImpl.java
index c82910be..1e412bc9 100644
--- a/src/main/java/org/apache/commons/dbcp2/cpdsadapter/PooledConnectionImpl.java
+++ b/src/main/java/org/apache/commons/dbcp2/cpdsadapter/PooledConnectionImpl.java
@@ -433,10 +433,7 @@ final class PooledConnectionImpl
      */
     void notifyListeners() {
         final ConnectionEvent event = new ConnectionEvent(this);
-        final Object[] listeners = eventListeners.toArray();
-        for (final Object listener : listeners) {
-            ((ConnectionEventListener) listener).connectionClosed(event);
-        }
+        new ArrayList<>(eventListeners).forEach(listener -> listener.connectionClosed(event));
     }
 
     /**
diff --git a/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSourceFactory.java b/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSourceFactory.java
index f7d9f58a..adf29c33 100644
--- a/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSourceFactory.java
+++ b/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSourceFactory.java
@@ -24,7 +24,6 @@ import java.util.ArrayList;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Properties;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -58,10 +57,11 @@ abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
     public static void closeAll() throws ListException {
         // Get iterator to loop over all instances of this data source.
         final List<Throwable> exceptionList = new ArrayList<>(INSTANCE_MAP.size());
-        for (final Entry<String, InstanceKeyDataSource> next : INSTANCE_MAP.entrySet()) {
+        INSTANCE_MAP.entrySet().forEach(entry -> {
             // Bullet-proof to avoid anything else but problems from InstanceKeyDataSource#close().
-            if (next != null) {
-                @SuppressWarnings("resource") final InstanceKeyDataSource value = next.getValue();
+            if (entry != null) {
+                @SuppressWarnings("resource")
+                final InstanceKeyDataSource value = entry.getValue();
                 if (value != null) {
                     try {
                         value.close();
@@ -70,7 +70,7 @@ abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
                     }
                 }
             }
-        }
+        });
         INSTANCE_MAP.clear();
         if (!exceptionList.isEmpty()) {
             throw new ListException("Could not close all InstanceKeyDataSource instances.", exceptionList);
diff --git a/src/main/java/org/apache/commons/dbcp2/datasources/PerUserPoolDataSource.java b/src/main/java/org/apache/commons/dbcp2/datasources/PerUserPoolDataSource.java
index 486aec16..f419bbce 100644
--- a/src/main/java/org/apache/commons/dbcp2/datasources/PerUserPoolDataSource.java
+++ b/src/main/java/org/apache/commons/dbcp2/datasources/PerUserPoolDataSource.java
@@ -102,13 +102,13 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * @since 2.3.0
      */
     public void clear() {
-        for (final PooledConnectionManager manager : managers.values()) {
+        managers.values().forEach(manager -> {
             try {
                 getCPDSConnectionFactoryPool(manager).clear();
             } catch (final Exception ignored) {
                 // ignore and try to close others.
             }
-        }
+        });
         InstanceKeyDataSourceFactory.removeInstance(getInstanceKey());
     }
 
@@ -117,12 +117,9 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      *
      * @see org.apache.commons.pool2.ObjectPool#close()
      */
-    @SuppressWarnings("resource")
     @Override
     public void close() {
-        for (final PooledConnectionManager manager : managers.values()) {
-            Utils.closeQuietly(getCPDSConnectionFactoryPool(manager));
-        }
+        managers.values().forEach(manager -> Utils.closeQuietly(getCPDSConnectionFactoryPool(manager)));
         InstanceKeyDataSourceFactory.removeInstance(getInstanceKey());
     }