Normalise EOL
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..bec231c
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,16 @@
+#   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.
+
+* text=auto
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 86248ad..dd57c39 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -1,85 +1,85 @@
-# 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.

-

-name: "CodeQL"

-

-on:

-  push:

-    branches: [ master ]

-  pull_request:

-    # The branches below must be a subset of the branches above

-    branches: [ master ]

-  schedule:

-    - cron: '33 9 * * 4'

-

-permissions:

-  contents: read

-

-jobs:

-  analyze:

-    name: Analyze

-    runs-on: ubuntu-latest

-    permissions:

-      actions: read

-      contents: read

-      security-events: write

-

-    strategy:

-      fail-fast: false

-      matrix:

-        language: [ 'java' ]

-        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]

-        # Learn more about CodeQL language support at https://git.io/codeql-language-support

-

-    steps:

-    - name: Checkout repository

+# 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.
+
+name: "CodeQL"
+
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+    # The branches below must be a subset of the branches above
+    branches: [ master ]
+  schedule:
+    - cron: '33 9 * * 4'
+
+permissions:
+  contents: read
+
+jobs:
+  analyze:
+    name: Analyze
+    runs-on: ubuntu-latest
+    permissions:
+      actions: read
+      contents: read
+      security-events: write
+
+    strategy:
+      fail-fast: false
+      matrix:
+        language: [ 'java' ]
+        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
+        # Learn more about CodeQL language support at https://git.io/codeql-language-support
+
+    steps:
+    - name: Checkout repository
       uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
       with:
         persist-credentials: false
     - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
-      with:

-        path: ~/.m2/repository

-        key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}

-        restore-keys: |

-          ${{ runner.os }}-maven-

-

-    # Initializes the CodeQL tools for scanning.

-    - name: Initialize CodeQL

+      with:
+        path: ~/.m2/repository
+        key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+        restore-keys: |
+          ${{ runner.os }}-maven-
+
+    # Initializes the CodeQL tools for scanning.
+    - name: Initialize CodeQL
       uses: github/codeql-action/init@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c    # 3.28.1
-      with:

-        languages: ${{ matrix.language }}

-        # If you wish to specify custom queries, you can do so here or in a config file.

-        # By default, queries listed here will override any specified in a config file.

-        # Prefix the list here with "+" to use these queries and those in the config file.

-        # queries: ./path/to/local/query, your-org/your-repo/queries@main

-

-    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).

-    # If this step fails, then you should remove it and run the build manually (see below)

-    - name: Autobuild

+      with:
+        languages: ${{ matrix.language }}
+        # If you wish to specify custom queries, you can do so here or in a config file.
+        # By default, queries listed here will override any specified in a config file.
+        # Prefix the list here with "+" to use these queries and those in the config file.
+        # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
+    # If this step fails, then you should remove it and run the build manually (see below)
+    - name: Autobuild
       uses: github/codeql-action/autobuild@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c    # 3.28.1
-

-    # â„šī¸ Command-line programs to run using the OS shell.

-    # 📚 https://git.io/JvXDl

-

-    # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines

-    #    and modify them (or add more) to build your code if your project

-    #    uses a compiled language

-

-    #- run: |

-    #   make bootstrap

-    #   make release

-

-    - name: Perform CodeQL Analysis

+
+    # â„šī¸ Command-line programs to run using the OS shell.
+    # 📚 https://git.io/JvXDl
+
+    # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines
+    #    and modify them (or add more) to build your code if your project
+    #    uses a compiled language
+
+    #- run: |
+    #   make bootstrap
+    #   make release
+
+    - name: Perform CodeQL Analysis
       uses: github/codeql-action/analyze@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c    # 3.28.1
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 36a84d4..9f74c52 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -1,52 +1,52 @@
-# 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.

-

-name: Java CI

-

-on: [push, pull_request]

-

-permissions:

-  contents: read

-

-jobs:

-  build:

-

-    runs-on: ubuntu-latest

-    continue-on-error: ${{ matrix.experimental }}

-    strategy:

-      matrix:

-        java: [ 11, 17, 21, 23 ]

-        experimental: [false]

-        include:

-          - java: 24-ea

-            experimental: true

-        

-    steps:

+# 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.
+
+name: Java CI
+
+on: [push, pull_request]
+
+permissions:
+  contents: read
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+    continue-on-error: ${{ matrix.experimental }}
+    strategy:
+      matrix:
+        java: [ 11, 17, 21, 23 ]
+        experimental: [false]
+        include:
+          - java: 24-ea
+            experimental: true
+        
+    steps:
     - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
       with:
         persist-credentials: false
     - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
-      with:

-        path: ~/.m2/repository

-        key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}

-        restore-keys: |

-          ${{ runner.os }}-maven-

-    - name: Set up JDK ${{ matrix.java }}

+      with:
+        path: ~/.m2/repository
+        key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+        restore-keys: |
+          ${{ runner.os }}-maven-
+    - name: Set up JDK ${{ matrix.java }}
       uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
-      with:

-        distribution: 'temurin'

-        java-version: ${{ matrix.java }}

-    - name: Build with Maven

-      run: mvn --errors --show-version --batch-mode --no-transfer-progress

+      with:
+        distribution: 'temurin'
+        java-version: ${{ matrix.java }}
+    - name: Build with Maven
+      run: mvn --errors --show-version --batch-mode --no-transfer-progress
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 0265a2f..a0777ca 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -1,459 +1,459 @@
-<?xml version="1.0"?>

-<!--

- 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.

--->

-

-<!--

-This file is used by the maven-changes-plugin to generate the release notes.

-Useful ways of finding items to add to this file are:

-

-1.  Add items when you fix a bug or add a feature (this makes the

-release process easy :-).

-

-2.  Do a bugzilla search for tickets closed since the previous release.

-

-3.  Use the report generated by the maven-changelog-plugin to see all

-CVS commits.  Set the project.properties' maven.changelog.range

-property to the number of days since the last release.

-

-To regenerate the RELEASE-NOTES.txt:

-

-mvn changes:announcement-generate -Prelease-notes [-Dchanges.version=m.n]

-

-The <action> type attribute can be add,update,fix,remove.

--->

-

-<document xmlns="http://maven.apache.org/changes/2.0.0"

-    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

-    xsi:schemaLocation="http://maven.apache.org/changes/2.0.0 https://maven.apache.org/plugins/maven-changes-plugin/xsd/changes-2.0.0.xsd">

-  <properties>

-    <title>Apache Commons DBUtils Release Notes</title>

-  </properties>

-  <body>

-    <release version="1.9.0" date="YYYY-MM-DD" description="New features and bug fixes.">

-      <!-- FIX -->

-      <action type="fix" dev="ggregory" due-to="sanjanarampurkottur01, Gary Gregory">Switch on String instead of cascading if-else in MockResultSet #220.</action>

-      <action type="fix" dev="ggregory" due-to="sanjanarampurkottur01, Gary Gregory">Encapsulate test fields #222.</action>

-      <action type="fix" dev="ggregory" due-to="Gary Gregory">Fix BeanProcessor SpotBugs CT: Be wary of letting constructors throw exceptions. (CT_CONSTRUCTOR_THROW).</action>

-      <action type="fix" dev="ggregory" due-to="Gary Gregory">Fix StatementConfiguration SpotBugs CT: Be wary of letting constructors throw exceptions. (CT_CONSTRUCTOR_THROW).</action>

-      <action type="fix" dev="ggregory" due-to="Gary Gregory">ResultSetIterator.next() does not check if it is already at the end of the result set (SpotBugs CheckResultSet).</action>

-      <action type="fix" dev="ggregory" due-to="Gary Gregory">Replace BasicRowProcessor use of Locale.ENGLISH with Locale.ROOT.</action>

-      <action type="fix" dev="ggregory" due-to="Gary Gregory">AbstractQueryRunner.fillStatementWithBean(PreparedStatement, Object, String...) now throws IllegalArgumentException instead of RuntimeException.</action>

-      <action type="fix" dev="ggregory" due-to="strangelookingnerd, Gary Gregory">Migrate tests to JUnit5 #324.</action> 

-      <!-- ADD -->

-      <!-- UPDATE -->

-      <action dev="ggregory" type="update" due-to="Gary Gregory">Bump Java from 8 to 11.</action>

-      <action dev="ggregory" type="update" due-to="Gary Gregory">Bump commons-parent from 62 to 78 #256, #277, #295, #301, #305, #308, #314.</action>

-      <action dev="ggregory" type="update" due-to="Gary Gregory">Bump org.mockito:mockito-core from 4.11.0 to 5.14.2 #325</action>

-      <action dev="ggregory" type="update" due-to="Dependabot, Gary Gregory">Bump org.mockito:mockito-junit-jupiter from 5.14.2 to 5.15.2 #336.</action>

-    </release>

-    <release version="1.8.1" date="2023-09-09" description="New features and bug fixes.">

-      <!-- FIX -->

-      <action type="fix" dev="ggregory" issue="DBUTILS-151" due-to="mark, Gary Gregory">

-        Module org.apache.commons.dbutils does not declare `uses`.

-      </action>

-      <!-- ADD -->

-      <!-- UPDATE -->

-      <action dev="ggregory" type="update" due-to="Gary Gregory">

-        Bump commons-parent from 61 to 62.

-      </action>

-    </release>

-    <release version="1.8.0" date="2023-08-01" description="New features and bug fixes.">

-      <!-- FIX -->

-      <action dev="thecarlhall" type="fix" issue="DBUTILS-131" due-to="yairlenga">

-        Speedup query calls without parameters; Use PreparedStatement only when parameters are present.

-      </action>

-      <action dev="thecarlhall" type="fix">

-        Always copy Date, Time, Timestamp on get and set in SqlNullCheckedResultSet.

-      </action>

-      <action dev="ggregory" type="update" issue="DBUTILS-135" due-to="hdevalke">

-        BeanProcessor is not thread safe since [DBUTILS-124].

-      </action>

-      <action dev="ggregory" type="update" issue="DBUTILS-137" due-to="Gary Gregory">

-        Inefficient allocation of Maps in org.apache.commons.dbutils.BasicRowProcessor.toMap(ResultSet).

-      </action>

-      <action type="fix" dev="ggregory" issue="DBUTILS-138" due-to="Stefano Lissa, Gary Gregory">

-        org.apache.commons.dbutils.QueryRunner.query(Connection, boolean, String, ResultSetHandler, Object...) Exception in closing statement leave connections open.

-      </action>

-      <action type="fix" dev="ggregory" issue="DBUTILS-139" due-to="Gary Gregory">

-        Update Java requirement from version 6 to 8.

-      </action>

-      <action due-to="thecarlhall" type="update">

-        clirr, checkstyle, and spotbugs configured as part of default build.

-      </action>

-      <action dev="ggregory" due-to="Amey Jadiye" type="fix">

-        Correction of coverage badge #10.

-      </action>

-      <action dev="ggregory" due-to="Gary Gregory" type="fix">

-        Manage JDBC objects using try-with-resources.

-      </action>

-      <action dev="ggregory" due-to="Gary Gregory" type="fix">

-        ResultSet not closed in QueryRunner.insert(Connection, String, ResultSetHandler, Object...).

-      </action>

-      <action dev="ggregory" due-to="Gary Gregory" type="fix">

-        ResultSet not closed in QueryRunner.insertBatch(Connection, String, ResultSetHandler, Object[][]).

-      </action>

-      <action dev="ggregory" due-to="Gary Gregory" type="fix">

-        AbstractQueryRunner.fillStatementWithBean(PreparedStatement, Object, String...) now throws IllegalStateException instead of RuntimeException.

-      </action>

-      <!-- ADD -->

-      <action dev="thecarlhall" type="add" issue="PR/9" due-to="rewerma">

-        Add @Column annotation to hint the field name instead of dissecting the get method name.

-      </action>

-      <action dev="ggregory" type="add" issue="DBUTILS-136" due-to="Matthew Hall, Gary Gregory">

-        CaseInsensitiveHashMap cannot be accessed by subclasses of BasicRowProcessor; add org.apache.commons.dbutils.BasicRowProcessor.createCaseInsensitiveHashMap(int).

-      </action>

-      <action dev="ggregory" type="add">

-        Add github/codeql-action #115.

-      </action>

-      <action type="fix" dev="ggregory" due-to="step-security-bot, Gary Gregory">

-        [StepSecurity] ci: Harden GitHub Actions #191.

-      </action>

-      <action dev="ggregory" type="add" due-to="Gary Gregory">

-        Add StatementConfiguration.StatementConfiguration(Integer, Integer, Integer, Integer, Duration).

-      </action>

-      <action dev="ggregory" type="add" due-to="Gary Gregory">

-        Add StatementConfiguration.getQueryTimeoutDuration().

-      </action>

-      <action dev="ggregory" type="add" due-to="Gary Gregory">

-        Add StatementConfiguration.Builder.queryTimeout(Duration).

-      </action>

-      <!-- UPDATE -->

-      <action dev="ggregory" type="update" due-to="Dependabot, Gary Gregory">

-        Bump actions/cache from 2 to 3.0.11 #109, #141, #145.

-      </action>

-      <action dev="ggregory" type="update" due-to="Dependabot, Gary Gregory">

-        Bump actions/checkout from 1 to 3.1.10, #44, #23, #48, #75, #93, #143.

-      </action>

-      <action dev="ggregory" type="update" due-to="Dependabot, Gary Gregory">

-        Bump actions/setup-java from 1.4.0 to 3.5.1 #40.

-      </action>

-      <action dev="ggregory" type="update" due-to="Gary Gregory">

-        Bump actions/upload-artifact from 3.1.0 to 3.1.1 #150.

-      </action>

-      <action dev="ggregory" type="update" due-to="Dependabot">

-        Bump commons-parent from 50 to 61 #14, #113, #139, #168, #189.

-      </action>

-      <action dev="ggregory" type="update" due-to="Dependabot, Gary Gregory">

-        Bump junit from 4.12 to 5.9.1 vintage #16, #42, #58.

-      </action>

-      <action dev="ggregory" type="update" due-to="Dependabot">

-        Bump mockito-core from 3.2.4 to 4.8.1 #18, #21, #46, #53, #97, #103, #111, #116, #122, #131. #137, #151.

-      </action>

-      <action dev="ggregory" type="update" due-to="Dependabot, Gary Gregory">

-        Bump spotbugs-maven-plugin from 3.1.12.2 to 4.4.2, #17, #45, #52.

-      </action>

-      <action dev="ggregory" type="update" due-to="Gary Gregory">

-        Bump spotbugs from 3.1.12.2 to 4.2.3.

-      </action>

-      <action dev="ggregory" type="update" due-to="Gary Gregory, Dependabot">

-        Bump maven-checkstyle-plugin from 3.1.0 to 3.2.0 #56, #132.

-      </action>

-      <action dev="ggregory" type="update" due-to="Gary Gregory, Dependabot">

-        Bump checkstyle from 8.28 to 9.3 #20, #47.

-      </action>

-      <action dev="ggregory" type="update" due-to="Gary Gregory">

-        Bump commons.japicmp.version from 0.14.3 to 0.16.0.

-      </action>

-    </release>

-    <release version="1.7" date="2017-07-20" description="Bug fixes and separate column &amp; property handlers using SPI">

-      <action dev="sebb" type="update" issue="DBUTILS-117" due-to="Vadim Smirnov">

-        Error handling possible getParameterMetaData() results

-        - allow for null return

-        - handle SQLFeatureNotSupportedException

-      </action>

-      <action dev="britter" type="update" issue="DBUTILS-119" due-to="Michael Akerman">

-        Correct errors in BeanMapHandler Javadoc

-      </action>

-      <action dev="thecarlhall" type="add" issue="DBUTILS-121">

-        Add getWriteMethod to BeanProcessor to allow subclasses to influence which write method is used

-      </action>

-      <action dev="thecarlhall" type="fix" issue="DBUTILS-82" due-to="Kenshi Toriumi">

-        Change method contracts to allow extended class types when returning a bean populated from a query

-      </action>

-      <action dev="thecarlhall" type="fix" issue="DBUTILS-89" due-to="Adam Dyga">

-        Add method in BeanProcessor to populate an existing bean

-      </action>

-      <action dev="thecarlhall" type="fix" issue="DBUTILS-70" due-to="Michael Akerman">

-        Add ability to configure statements used in QueryRunner

-      </action>

-      <action dev="thecarlhall" type="add" issue="DBUTILS-50" due-to="Dan Fabulich">

-        Support CallableStatement "out" parameters

-      </action>

-      <action dev="thecarlhall" type="add" issue="DBUTILS-124">

-        Implement column and property handlers using Java's service interfaces.

-      </action>

-      <action dev="thecarlhall" type="update" issue="PR/1" due-to="Michael Hausegger">

-        Created some Unit Tests to increase code coverage.

-      </action>

-    </release>

-    <release version="1.6" date="2014-07-20" description="Bug fixes and addition of insert methods">

-      <action dev="britter" type="fix" issue="DBUTILS-110">

-        ArrayHandler should return an empty array when handle has no rows

-      </action>

-      <action dev="britter" type="fix" issue="DBUTILS-114" due-to="Michael Osipov">

-        Order of columns not retained in BasicRowProcessor with HashMap

-      </action>

-      <action dev="britter" type="fix" issue="DBUTILS-118" due-to="Feysal Rujbally, Daniele Cremonini">

-        BeanProcessor not returning nanoseconds

-      </action>

-      <action dev="britter" type="add" issue="DBUTILS-113" due-to="Graylin Kim, Michael Osipov">

-        Add support for conversion of ResultSet strings to enums in the BeanProcessor

-      </action>

-      <action dev="sebb" type="update" issue="DBUTILS-85">

-        In BeanProcessor#isCompatibleType, can Integer.class.isInstance(value) be replaced by value instanceof Integer (etc)?

-        Simplified code by using instanceof.

-      </action>

-      <action dev="sebb" due-to="Niall Pemberton" type="fix" issue="DBUTILS-106">

-        DBUtils can't build using JDK 1.7 - DriverProxy needs to implement getParentLogger()

-        Add dynamic invocation.

-      </action>

-      <action dev="wspeirs" due-to="Micah Huff" type="add" issue="DBUTILS-108">

-        Create functionality to return auto-generated keys in batches of SQL inserts

-      </action>

-      <action dev="wspeirs" due-to="PB" type="add" issue="DBUTILS-107">

-        Patch QueryLoader to also load from XML properties files

-      </action>

-      <action dev="wspeirs" due-to="xiaofei.xu" type="fix" issue="DBUTILS-100">

-        Updated the use of getColumnName to try getColumnLabel first

-      </action>

-      <action dev="simonetripodi" due-to="Moandji Ezana" type="add" issue="DBUTILS-98">

-        Add missing Javadoc to QueryRunner#insert

-      </action>

-      <action dev="simonetripodi" type="add" issue="DBUTILS-97">

-        Add an Abstract ResultSetHandler implementation in order to reduce redundant 'resultSet' variable invocation

-      </action>

-      <action dev="simonetripodi" due-to="yuyf" type="fix" issue="DBUTILS-96">

-        DbUtils#loadDriver(ClassLoader,String) makes DriverManager throwing "No suitable driver found for jdbc"

-        if ClassLoader is not the System's one

-      </action>

-      <action dev="wspeirs" due-to="Moandji Ezana" type="add" issue="DBUTILS-87">

-        Added insert methods to QueryRunner and AsyncQueryRunner that return the generated key.

-      </action>

-    </release>

-    <release version="1.5" date="2012-07-20" description="Bug fixes and addition of BeanMapHandler">

-      <action dev="simonetripodi" due-to="Benedikt Ritter" type="update" issue="DBUTILS-94">

-        Provide test coverage for org.apache.commons.dbutils.DbUtils

-      </action>

-      <action dev="simonetripodi" due-to="Stevo Slavic" type="fix" issue="DBUTILS-93">

-        Source assembly artifact fails to build a site because of missing pmd-ruleset.xml

-      </action>

-      <action dev="simonetripodi" due-to="Stevo Slavic" type="update" issue="DBUTILS-91">

-        Enhance BasicRowProcessor to have row mapping easier to configure

-      </action>

-      <action due-to="wspeirs" type="update">

-        Updated pom.xml: Java 1.6 now required, clirr and compiler plugin removed

-      </action>

-      <action due-to="Tiago Cavaleiro" dev="wspeirs" type="fix" issue="DBUTILS-84">

-        BeanProcessor method processColumn should take SQLXML in consideration

-      </action>

-      <action dev="wspeirs" type="update" issue="DBUTILS-77">

-        Updated documentation to better reflect the use of pmdKnownBroken

-      </action>

-      <action due-to="Sebb" type="fix" issue="DBUTILS-73">

-        Added a fixed Locale (Locale.ENGLISH) to all toLowerCase calls in BasicRowProcessor

-      </action>

-      <action due-to="Michael Osipov" type="add" issue="DBUTILS-67">

-        Added BeanMapHandler

-      </action>

-      <action due-to="Michael Osipov" type="update" issue="DBUTILS-66">

-        Added generics to ScalarHandler, ColumnHandler, and KeyedHandler

-      </action>

-    </release>

-    <release version="1.4" date="2011-10-23" description="Bug fixes and addition of asynchronous QueryLoader">

-      <action type="fix" issue="DBUTILS-81">

-        DbUtils.loadDriver() uses Class.forName()

-      </action>

-      <action type="fix" issue="DBUTILS-80">

-        DbUtils.loadDriver catches Throwable

-      </action>

-      <action type="fix" issue="DBUTILS-65">

-        Duplicate code introduced during Java 1.5 branch merge

-      </action>

-      <action type="fix" issue="DBUTILS-79">

-        fillStatement doesn't complain when there are too few parameters

-      </action>

-      <action type="update" issue="DBUTILS-75">

-        efficient usage from findbugs

-      </action>

-      <action dev="wspeirs" type="add" issue="DBUTILS-78">

-        Add asynchronous batch, query, and update calls

-      </action>

-    </release>

-    <release version="1.3" date="2009-11-04" description="Adds Java5 generics and varargs">

-      <action dev="dfabulich" type="add" issue="DBUTILS-48">

-        Java 1.5 generics and varargs

-      </action>

-      <action dev="dfabulich" type="update" issue="DBUTILS-57">

-        BeanProcessor#mapColumnsToProperties now prefers to use column labels over column names (where aliases are not set, these should be identical)

-      </action>

-      <action dev="dfabulich" type="update" issue="DBUTILS-58">

-        Setting pmdKnownBroken in QueryRunner constructor now completely ignores ParameterMetaData

-      </action>

-      <action dev="dfabulich" type="fix" issue="DBUTILS-60">

-        Fixed error message in QueryRunner#rethrow

-      </action>

-    </release>

-    <release version="1.2" date="2009-03-06" description="Another round of fixes; deprecates methods in preparation for varargs in java5">

-      <action dev="dfabulich" type="update" issue="DBUTILS-52">

-        Removed setDataSource method to guarantee thread safety

-      </action>

-      <action dev="sebb" type="update" issue="DBUTILS-51">

-        Made numerous private instance members final to guarantee thread safety; changed protected member of KeyedHandler to final

-      </action>

-      <action dev="bayard" type="remove">

-        Remove old Maven1/Ant build scripts

-      </action>

-      <action dev="dfabulich" type="update" issue="DBUTILS-29">

-        Support bean property to SQL IN parameter mapping

-      </action>

-      <action dev="dfabulich" type="fix" issue="DBUTILS-31">

-        fillStatement setNull bug with the Postgres/Derby JDBC driver (and others)

-      </action>

-      <action dev="dfabulich" type="update" issue="DBUTILS-33">

-        Make GenericListHandler (now AbstractListHandler) public

-      </action>

-      <action dev="dfabulich" type="update" issue="DBUTILS-34">

-        BasicRowProcessor loses any information on database field case

-      </action>

-      <action dev="dfabulich" type="update" issue="DBUTILS-37">

-        BeanListHandler#handle(ResultSet) is not optimal

-      </action>

-      <action dev="dfabulich" type="fix" issue="DBUTILS-40">

-        NullPointerException occurred at rethrow method

-      </action>

-      <action dev="dfabulich" type="update" issue="DBUTILS-42">

-        Object with Long or Decimal got initial zero value while database field is null

-      </action>

-      <action dev="dennisl" type="update" issue="DBUTILS-38">

-        example documentation page, update query

-      </action>

-      <action dev="bayard" type="fix" issue="DBUTILS-36">

-        Add serialVersionUID to BasicRowProcessor.CaseInsensitiveHashMap

-      </action>

-    </release>

-    <release version="1.1" date="2006-12-01" description="Last couple of years of fixes">

-      <action dev="bayard" type="fix" issue="DBUTILS-32">

-        Tests fail to build under 1.6, and warning while compiling source

-      </action>

-      <action dev="bayard" type="fix" issue="DBUTILS-1">

-        BeanListHandler and BeanHandler fail to support java.sql.Date()

-      </action>

-      <action dev="bayard" type="update" issue="DBUTILS-16">

-        ResultSetRowProcessor abstract handler and some classes rework

-      </action>

-      <action dev="bayard" type="fix" issue="DBUTILS-3">

-        Setting bean properties fails silently

-      </action>

-      <action dev="dgraham" type="fix" issue="DBUTILS-9">

-        MockResultSet needs to handle equals and hashCode

-      </action>

-      <action dev="bayard" type="fix" issue="DBUTILS-7">

-        MockResultSet: Throw UnsupportedOperationException for not implemented methods

-      </action>

-      <action dev="dgraham" type="add" issue="DBUTILS-20">

-        Implement Pluggable Adaptors to Make BeanHandler Smarter

-      </action>

-      <action dev="dgraham" type="update" issue="DBUTILS-15">

-        Patch for extending BasicRowProcessor

-      </action>

-      <action dev="dgraham" type="add" issue="DBUTILS-12">

-        Protected QueryRunner.close() methods

-      </action>

-      <action dev="dgraham" type="update" issue="DBUTILS-23">

-        Updated docs for example.html page (select AS)

-      </action>

-      <action dev="dgraham" type="add" issue="DBUTILS-4">

-        Added protected ResultSetIterator.rethrow() method to wrap SQLExceptions in

-        RuntimeExceptions.

-      </action>

-      <action dev="dgraham" type="update" issue="DBUTILS-5">

-        Added SQLState and error code to rethrown SQLExceptions.

-      </action>

-      <action dev="dgraham" type="add" issue="DBUTILS-25">

-        Added KeyedHandler to create a Map of Maps from a ResultSet.

-      </action>

-      <action dev="dgraham" type="update" issue="DBUTILS-2">

-        Use current class' ClassLoader instead of QueryLoader's ClassLoader

-        in loadQueries().

-      </action>

-      <action dev="dgraham" type="update" issue="DBUTILS-22">

-        Made QueryLoader.loadQueries() protected so subclasses can use query

-        repositories other than properties files.

-      </action>

-      <action dev="dgraham" type="update">

-        QueryRunner now calls getDataSource() internally any time it needs access

-        to its DataSource object to allow subclasses to provide different behavior.

-      </action>

-      <action dev="dgraham" type="add">

-        Added DbUtils.rollbackAndClose() and DbUtils.rollbackAndCloseQuietly().

-      </action>

-      <action dev="dgraham" type="update" issue="DBUTILS-26">

-        Call ResultSet.getTimestamp() in BeanProcessor.processColumn() if

-        the bean property is a java.sql.Timestamp.  Oracle's getObject()

-        implementation returns its own incompatible Timestamp class.

-      </action>

-      <action dev="dgraham" type="update" issue="DBUTILS-18">

-        Changed QueryRunner.fillStatement() null handling

-        to use Types.VARCHAR instead of Types.OTHER.  This works for the

-        following tested drivers: Firebird 1.5/firebirdsql 1.5RC3,

-        Oracle 9/ Thin driver, MySQL 4.0/Msql Connecttor 3.0 and mm.mysql

-        2.0.4 MaxDB 7.5, HSQLDB 1.7.1, and MS Access/ODBC Bridge.

-      </action>

-      <action dev="dgraham" type="add" issue="DBUTILS-21">

-        Added a protected QueryRunner.prepareConnection() method to

-        allow subclasses to customize the Connections retrieved from

-        the DataSource before they're used.

-      </action>

-      <action dev="dgraham" type="add">

-        Refactored bean handling from BasicRowProcessor into new

-        BeanProcessor class.  This also fixes the common problem with

-        Oracle NUMERIC fields not being set into bean properties.

-      </action>

-      <action dev="dgraham" type="add" issue="DBUTILS-13">

-        Added QueryRunner.batch() methods for batch updates.

-      </action>

-      <action dev="dgraham" type="add" issue="DBUTILS-11">

-        Added new ResultSetHandler implementation, ColumnListHandler, that

-        converts one ResultSet column into a List of Objects.

-      </action>

-    </release>

-    <release version="1.0" date="2003-11-10" description="First release of DbUtils">

-      <action type="add">

-        This is the first release of the Commons DbUtils package.  DbUtils

-        is a small set of classes designed to make working with JDBC easier.

-      </action>

-      <action type="add">

-        QueryRunner class with ResultSetHandler interface allow you to easily query or

-        update a database and handle the ResultSet.  Several useful implementations

-        of the ResultSetHandler interface are located in the

-        org.apache.commons.dbutils.handlers.* package.

-      </action>

-      <action type="add">

-        ResultSet wrappers that decorate ResultSets with specialized

-        behavior.  See the classes in the org.apache.commons.dbutils.wrappers.*

-        package for details.

-      </action>

-      <action type="add">

-        Dynamic JDBC API interface implementations via the standard

-        java.lang.reflect.Proxy class.  This allows you to implement JDBC

-        interfaces such as ResultSet at runtime to avoid API version

-        incompatibilities.  See org.apache.commons.dbutils.ProxyFactory

-        for details.

-      </action>

-    </release>

-  </body>

-</document>

+<?xml version="1.0"?>
+<!--
+ 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.
+-->
+
+<!--
+This file is used by the maven-changes-plugin to generate the release notes.
+Useful ways of finding items to add to this file are:
+
+1.  Add items when you fix a bug or add a feature (this makes the
+release process easy :-).
+
+2.  Do a bugzilla search for tickets closed since the previous release.
+
+3.  Use the report generated by the maven-changelog-plugin to see all
+CVS commits.  Set the project.properties' maven.changelog.range
+property to the number of days since the last release.
+
+To regenerate the RELEASE-NOTES.txt:
+
+mvn changes:announcement-generate -Prelease-notes [-Dchanges.version=m.n]
+
+The <action> type attribute can be add,update,fix,remove.
+-->
+
+<document xmlns="http://maven.apache.org/changes/2.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/changes/2.0.0 https://maven.apache.org/plugins/maven-changes-plugin/xsd/changes-2.0.0.xsd">
+  <properties>
+    <title>Apache Commons DBUtils Release Notes</title>
+  </properties>
+  <body>
+    <release version="1.9.0" date="YYYY-MM-DD" description="New features and bug fixes.">
+      <!-- FIX -->
+      <action type="fix" dev="ggregory" due-to="sanjanarampurkottur01, Gary Gregory">Switch on String instead of cascading if-else in MockResultSet #220.</action>
+      <action type="fix" dev="ggregory" due-to="sanjanarampurkottur01, Gary Gregory">Encapsulate test fields #222.</action>
+      <action type="fix" dev="ggregory" due-to="Gary Gregory">Fix BeanProcessor SpotBugs CT: Be wary of letting constructors throw exceptions. (CT_CONSTRUCTOR_THROW).</action>
+      <action type="fix" dev="ggregory" due-to="Gary Gregory">Fix StatementConfiguration SpotBugs CT: Be wary of letting constructors throw exceptions. (CT_CONSTRUCTOR_THROW).</action>
+      <action type="fix" dev="ggregory" due-to="Gary Gregory">ResultSetIterator.next() does not check if it is already at the end of the result set (SpotBugs CheckResultSet).</action>
+      <action type="fix" dev="ggregory" due-to="Gary Gregory">Replace BasicRowProcessor use of Locale.ENGLISH with Locale.ROOT.</action>
+      <action type="fix" dev="ggregory" due-to="Gary Gregory">AbstractQueryRunner.fillStatementWithBean(PreparedStatement, Object, String...) now throws IllegalArgumentException instead of RuntimeException.</action>
+      <action type="fix" dev="ggregory" due-to="strangelookingnerd, Gary Gregory">Migrate tests to JUnit5 #324.</action> 
+      <!-- ADD -->
+      <!-- UPDATE -->
+      <action dev="ggregory" type="update" due-to="Gary Gregory">Bump Java from 8 to 11.</action>
+      <action dev="ggregory" type="update" due-to="Gary Gregory">Bump commons-parent from 62 to 78 #256, #277, #295, #301, #305, #308, #314.</action>
+      <action dev="ggregory" type="update" due-to="Gary Gregory">Bump org.mockito:mockito-core from 4.11.0 to 5.14.2 #325</action>
+      <action dev="ggregory" type="update" due-to="Dependabot, Gary Gregory">Bump org.mockito:mockito-junit-jupiter from 5.14.2 to 5.15.2 #336.</action>
+    </release>
+    <release version="1.8.1" date="2023-09-09" description="New features and bug fixes.">
+      <!-- FIX -->
+      <action type="fix" dev="ggregory" issue="DBUTILS-151" due-to="mark, Gary Gregory">
+        Module org.apache.commons.dbutils does not declare `uses`.
+      </action>
+      <!-- ADD -->
+      <!-- UPDATE -->
+      <action dev="ggregory" type="update" due-to="Gary Gregory">
+        Bump commons-parent from 61 to 62.
+      </action>
+    </release>
+    <release version="1.8.0" date="2023-08-01" description="New features and bug fixes.">
+      <!-- FIX -->
+      <action dev="thecarlhall" type="fix" issue="DBUTILS-131" due-to="yairlenga">
+        Speedup query calls without parameters; Use PreparedStatement only when parameters are present.
+      </action>
+      <action dev="thecarlhall" type="fix">
+        Always copy Date, Time, Timestamp on get and set in SqlNullCheckedResultSet.
+      </action>
+      <action dev="ggregory" type="update" issue="DBUTILS-135" due-to="hdevalke">
+        BeanProcessor is not thread safe since [DBUTILS-124].
+      </action>
+      <action dev="ggregory" type="update" issue="DBUTILS-137" due-to="Gary Gregory">
+        Inefficient allocation of Maps in org.apache.commons.dbutils.BasicRowProcessor.toMap(ResultSet).
+      </action>
+      <action type="fix" dev="ggregory" issue="DBUTILS-138" due-to="Stefano Lissa, Gary Gregory">
+        org.apache.commons.dbutils.QueryRunner.query(Connection, boolean, String, ResultSetHandler, Object...) Exception in closing statement leave connections open.
+      </action>
+      <action type="fix" dev="ggregory" issue="DBUTILS-139" due-to="Gary Gregory">
+        Update Java requirement from version 6 to 8.
+      </action>
+      <action due-to="thecarlhall" type="update">
+        clirr, checkstyle, and spotbugs configured as part of default build.
+      </action>
+      <action dev="ggregory" due-to="Amey Jadiye" type="fix">
+        Correction of coverage badge #10.
+      </action>
+      <action dev="ggregory" due-to="Gary Gregory" type="fix">
+        Manage JDBC objects using try-with-resources.
+      </action>
+      <action dev="ggregory" due-to="Gary Gregory" type="fix">
+        ResultSet not closed in QueryRunner.insert(Connection, String, ResultSetHandler, Object...).
+      </action>
+      <action dev="ggregory" due-to="Gary Gregory" type="fix">
+        ResultSet not closed in QueryRunner.insertBatch(Connection, String, ResultSetHandler, Object[][]).
+      </action>
+      <action dev="ggregory" due-to="Gary Gregory" type="fix">
+        AbstractQueryRunner.fillStatementWithBean(PreparedStatement, Object, String...) now throws IllegalStateException instead of RuntimeException.
+      </action>
+      <!-- ADD -->
+      <action dev="thecarlhall" type="add" issue="PR/9" due-to="rewerma">
+        Add @Column annotation to hint the field name instead of dissecting the get method name.
+      </action>
+      <action dev="ggregory" type="add" issue="DBUTILS-136" due-to="Matthew Hall, Gary Gregory">
+        CaseInsensitiveHashMap cannot be accessed by subclasses of BasicRowProcessor; add org.apache.commons.dbutils.BasicRowProcessor.createCaseInsensitiveHashMap(int).
+      </action>
+      <action dev="ggregory" type="add">
+        Add github/codeql-action #115.
+      </action>
+      <action type="fix" dev="ggregory" due-to="step-security-bot, Gary Gregory">
+        [StepSecurity] ci: Harden GitHub Actions #191.
+      </action>
+      <action dev="ggregory" type="add" due-to="Gary Gregory">
+        Add StatementConfiguration.StatementConfiguration(Integer, Integer, Integer, Integer, Duration).
+      </action>
+      <action dev="ggregory" type="add" due-to="Gary Gregory">
+        Add StatementConfiguration.getQueryTimeoutDuration().
+      </action>
+      <action dev="ggregory" type="add" due-to="Gary Gregory">
+        Add StatementConfiguration.Builder.queryTimeout(Duration).
+      </action>
+      <!-- UPDATE -->
+      <action dev="ggregory" type="update" due-to="Dependabot, Gary Gregory">
+        Bump actions/cache from 2 to 3.0.11 #109, #141, #145.
+      </action>
+      <action dev="ggregory" type="update" due-to="Dependabot, Gary Gregory">
+        Bump actions/checkout from 1 to 3.1.10, #44, #23, #48, #75, #93, #143.
+      </action>
+      <action dev="ggregory" type="update" due-to="Dependabot, Gary Gregory">
+        Bump actions/setup-java from 1.4.0 to 3.5.1 #40.
+      </action>
+      <action dev="ggregory" type="update" due-to="Gary Gregory">
+        Bump actions/upload-artifact from 3.1.0 to 3.1.1 #150.
+      </action>
+      <action dev="ggregory" type="update" due-to="Dependabot">
+        Bump commons-parent from 50 to 61 #14, #113, #139, #168, #189.
+      </action>
+      <action dev="ggregory" type="update" due-to="Dependabot, Gary Gregory">
+        Bump junit from 4.12 to 5.9.1 vintage #16, #42, #58.
+      </action>
+      <action dev="ggregory" type="update" due-to="Dependabot">
+        Bump mockito-core from 3.2.4 to 4.8.1 #18, #21, #46, #53, #97, #103, #111, #116, #122, #131. #137, #151.
+      </action>
+      <action dev="ggregory" type="update" due-to="Dependabot, Gary Gregory">
+        Bump spotbugs-maven-plugin from 3.1.12.2 to 4.4.2, #17, #45, #52.
+      </action>
+      <action dev="ggregory" type="update" due-to="Gary Gregory">
+        Bump spotbugs from 3.1.12.2 to 4.2.3.
+      </action>
+      <action dev="ggregory" type="update" due-to="Gary Gregory, Dependabot">
+        Bump maven-checkstyle-plugin from 3.1.0 to 3.2.0 #56, #132.
+      </action>
+      <action dev="ggregory" type="update" due-to="Gary Gregory, Dependabot">
+        Bump checkstyle from 8.28 to 9.3 #20, #47.
+      </action>
+      <action dev="ggregory" type="update" due-to="Gary Gregory">
+        Bump commons.japicmp.version from 0.14.3 to 0.16.0.
+      </action>
+    </release>
+    <release version="1.7" date="2017-07-20" description="Bug fixes and separate column &amp; property handlers using SPI">
+      <action dev="sebb" type="update" issue="DBUTILS-117" due-to="Vadim Smirnov">
+        Error handling possible getParameterMetaData() results
+        - allow for null return
+        - handle SQLFeatureNotSupportedException
+      </action>
+      <action dev="britter" type="update" issue="DBUTILS-119" due-to="Michael Akerman">
+        Correct errors in BeanMapHandler Javadoc
+      </action>
+      <action dev="thecarlhall" type="add" issue="DBUTILS-121">
+        Add getWriteMethod to BeanProcessor to allow subclasses to influence which write method is used
+      </action>
+      <action dev="thecarlhall" type="fix" issue="DBUTILS-82" due-to="Kenshi Toriumi">
+        Change method contracts to allow extended class types when returning a bean populated from a query
+      </action>
+      <action dev="thecarlhall" type="fix" issue="DBUTILS-89" due-to="Adam Dyga">
+        Add method in BeanProcessor to populate an existing bean
+      </action>
+      <action dev="thecarlhall" type="fix" issue="DBUTILS-70" due-to="Michael Akerman">
+        Add ability to configure statements used in QueryRunner
+      </action>
+      <action dev="thecarlhall" type="add" issue="DBUTILS-50" due-to="Dan Fabulich">
+        Support CallableStatement "out" parameters
+      </action>
+      <action dev="thecarlhall" type="add" issue="DBUTILS-124">
+        Implement column and property handlers using Java's service interfaces.
+      </action>
+      <action dev="thecarlhall" type="update" issue="PR/1" due-to="Michael Hausegger">
+        Created some Unit Tests to increase code coverage.
+      </action>
+    </release>
+    <release version="1.6" date="2014-07-20" description="Bug fixes and addition of insert methods">
+      <action dev="britter" type="fix" issue="DBUTILS-110">
+        ArrayHandler should return an empty array when handle has no rows
+      </action>
+      <action dev="britter" type="fix" issue="DBUTILS-114" due-to="Michael Osipov">
+        Order of columns not retained in BasicRowProcessor with HashMap
+      </action>
+      <action dev="britter" type="fix" issue="DBUTILS-118" due-to="Feysal Rujbally, Daniele Cremonini">
+        BeanProcessor not returning nanoseconds
+      </action>
+      <action dev="britter" type="add" issue="DBUTILS-113" due-to="Graylin Kim, Michael Osipov">
+        Add support for conversion of ResultSet strings to enums in the BeanProcessor
+      </action>
+      <action dev="sebb" type="update" issue="DBUTILS-85">
+        In BeanProcessor#isCompatibleType, can Integer.class.isInstance(value) be replaced by value instanceof Integer (etc)?
+        Simplified code by using instanceof.
+      </action>
+      <action dev="sebb" due-to="Niall Pemberton" type="fix" issue="DBUTILS-106">
+        DBUtils can't build using JDK 1.7 - DriverProxy needs to implement getParentLogger()
+        Add dynamic invocation.
+      </action>
+      <action dev="wspeirs" due-to="Micah Huff" type="add" issue="DBUTILS-108">
+        Create functionality to return auto-generated keys in batches of SQL inserts
+      </action>
+      <action dev="wspeirs" due-to="PB" type="add" issue="DBUTILS-107">
+        Patch QueryLoader to also load from XML properties files
+      </action>
+      <action dev="wspeirs" due-to="xiaofei.xu" type="fix" issue="DBUTILS-100">
+        Updated the use of getColumnName to try getColumnLabel first
+      </action>
+      <action dev="simonetripodi" due-to="Moandji Ezana" type="add" issue="DBUTILS-98">
+        Add missing Javadoc to QueryRunner#insert
+      </action>
+      <action dev="simonetripodi" type="add" issue="DBUTILS-97">
+        Add an Abstract ResultSetHandler implementation in order to reduce redundant 'resultSet' variable invocation
+      </action>
+      <action dev="simonetripodi" due-to="yuyf" type="fix" issue="DBUTILS-96">
+        DbUtils#loadDriver(ClassLoader,String) makes DriverManager throwing "No suitable driver found for jdbc"
+        if ClassLoader is not the System's one
+      </action>
+      <action dev="wspeirs" due-to="Moandji Ezana" type="add" issue="DBUTILS-87">
+        Added insert methods to QueryRunner and AsyncQueryRunner that return the generated key.
+      </action>
+    </release>
+    <release version="1.5" date="2012-07-20" description="Bug fixes and addition of BeanMapHandler">
+      <action dev="simonetripodi" due-to="Benedikt Ritter" type="update" issue="DBUTILS-94">
+        Provide test coverage for org.apache.commons.dbutils.DbUtils
+      </action>
+      <action dev="simonetripodi" due-to="Stevo Slavic" type="fix" issue="DBUTILS-93">
+        Source assembly artifact fails to build a site because of missing pmd-ruleset.xml
+      </action>
+      <action dev="simonetripodi" due-to="Stevo Slavic" type="update" issue="DBUTILS-91">
+        Enhance BasicRowProcessor to have row mapping easier to configure
+      </action>
+      <action due-to="wspeirs" type="update">
+        Updated pom.xml: Java 1.6 now required, clirr and compiler plugin removed
+      </action>
+      <action due-to="Tiago Cavaleiro" dev="wspeirs" type="fix" issue="DBUTILS-84">
+        BeanProcessor method processColumn should take SQLXML in consideration
+      </action>
+      <action dev="wspeirs" type="update" issue="DBUTILS-77">
+        Updated documentation to better reflect the use of pmdKnownBroken
+      </action>
+      <action due-to="Sebb" type="fix" issue="DBUTILS-73">
+        Added a fixed Locale (Locale.ENGLISH) to all toLowerCase calls in BasicRowProcessor
+      </action>
+      <action due-to="Michael Osipov" type="add" issue="DBUTILS-67">
+        Added BeanMapHandler
+      </action>
+      <action due-to="Michael Osipov" type="update" issue="DBUTILS-66">
+        Added generics to ScalarHandler, ColumnHandler, and KeyedHandler
+      </action>
+    </release>
+    <release version="1.4" date="2011-10-23" description="Bug fixes and addition of asynchronous QueryLoader">
+      <action type="fix" issue="DBUTILS-81">
+        DbUtils.loadDriver() uses Class.forName()
+      </action>
+      <action type="fix" issue="DBUTILS-80">
+        DbUtils.loadDriver catches Throwable
+      </action>
+      <action type="fix" issue="DBUTILS-65">
+        Duplicate code introduced during Java 1.5 branch merge
+      </action>
+      <action type="fix" issue="DBUTILS-79">
+        fillStatement doesn't complain when there are too few parameters
+      </action>
+      <action type="update" issue="DBUTILS-75">
+        efficient usage from findbugs
+      </action>
+      <action dev="wspeirs" type="add" issue="DBUTILS-78">
+        Add asynchronous batch, query, and update calls
+      </action>
+    </release>
+    <release version="1.3" date="2009-11-04" description="Adds Java5 generics and varargs">
+      <action dev="dfabulich" type="add" issue="DBUTILS-48">
+        Java 1.5 generics and varargs
+      </action>
+      <action dev="dfabulich" type="update" issue="DBUTILS-57">
+        BeanProcessor#mapColumnsToProperties now prefers to use column labels over column names (where aliases are not set, these should be identical)
+      </action>
+      <action dev="dfabulich" type="update" issue="DBUTILS-58">
+        Setting pmdKnownBroken in QueryRunner constructor now completely ignores ParameterMetaData
+      </action>
+      <action dev="dfabulich" type="fix" issue="DBUTILS-60">
+        Fixed error message in QueryRunner#rethrow
+      </action>
+    </release>
+    <release version="1.2" date="2009-03-06" description="Another round of fixes; deprecates methods in preparation for varargs in java5">
+      <action dev="dfabulich" type="update" issue="DBUTILS-52">
+        Removed setDataSource method to guarantee thread safety
+      </action>
+      <action dev="sebb" type="update" issue="DBUTILS-51">
+        Made numerous private instance members final to guarantee thread safety; changed protected member of KeyedHandler to final
+      </action>
+      <action dev="bayard" type="remove">
+        Remove old Maven1/Ant build scripts
+      </action>
+      <action dev="dfabulich" type="update" issue="DBUTILS-29">
+        Support bean property to SQL IN parameter mapping
+      </action>
+      <action dev="dfabulich" type="fix" issue="DBUTILS-31">
+        fillStatement setNull bug with the Postgres/Derby JDBC driver (and others)
+      </action>
+      <action dev="dfabulich" type="update" issue="DBUTILS-33">
+        Make GenericListHandler (now AbstractListHandler) public
+      </action>
+      <action dev="dfabulich" type="update" issue="DBUTILS-34">
+        BasicRowProcessor loses any information on database field case
+      </action>
+      <action dev="dfabulich" type="update" issue="DBUTILS-37">
+        BeanListHandler#handle(ResultSet) is not optimal
+      </action>
+      <action dev="dfabulich" type="fix" issue="DBUTILS-40">
+        NullPointerException occurred at rethrow method
+      </action>
+      <action dev="dfabulich" type="update" issue="DBUTILS-42">
+        Object with Long or Decimal got initial zero value while database field is null
+      </action>
+      <action dev="dennisl" type="update" issue="DBUTILS-38">
+        example documentation page, update query
+      </action>
+      <action dev="bayard" type="fix" issue="DBUTILS-36">
+        Add serialVersionUID to BasicRowProcessor.CaseInsensitiveHashMap
+      </action>
+    </release>
+    <release version="1.1" date="2006-12-01" description="Last couple of years of fixes">
+      <action dev="bayard" type="fix" issue="DBUTILS-32">
+        Tests fail to build under 1.6, and warning while compiling source
+      </action>
+      <action dev="bayard" type="fix" issue="DBUTILS-1">
+        BeanListHandler and BeanHandler fail to support java.sql.Date()
+      </action>
+      <action dev="bayard" type="update" issue="DBUTILS-16">
+        ResultSetRowProcessor abstract handler and some classes rework
+      </action>
+      <action dev="bayard" type="fix" issue="DBUTILS-3">
+        Setting bean properties fails silently
+      </action>
+      <action dev="dgraham" type="fix" issue="DBUTILS-9">
+        MockResultSet needs to handle equals and hashCode
+      </action>
+      <action dev="bayard" type="fix" issue="DBUTILS-7">
+        MockResultSet: Throw UnsupportedOperationException for not implemented methods
+      </action>
+      <action dev="dgraham" type="add" issue="DBUTILS-20">
+        Implement Pluggable Adaptors to Make BeanHandler Smarter
+      </action>
+      <action dev="dgraham" type="update" issue="DBUTILS-15">
+        Patch for extending BasicRowProcessor
+      </action>
+      <action dev="dgraham" type="add" issue="DBUTILS-12">
+        Protected QueryRunner.close() methods
+      </action>
+      <action dev="dgraham" type="update" issue="DBUTILS-23">
+        Updated docs for example.html page (select AS)
+      </action>
+      <action dev="dgraham" type="add" issue="DBUTILS-4">
+        Added protected ResultSetIterator.rethrow() method to wrap SQLExceptions in
+        RuntimeExceptions.
+      </action>
+      <action dev="dgraham" type="update" issue="DBUTILS-5">
+        Added SQLState and error code to rethrown SQLExceptions.
+      </action>
+      <action dev="dgraham" type="add" issue="DBUTILS-25">
+        Added KeyedHandler to create a Map of Maps from a ResultSet.
+      </action>
+      <action dev="dgraham" type="update" issue="DBUTILS-2">
+        Use current class' ClassLoader instead of QueryLoader's ClassLoader
+        in loadQueries().
+      </action>
+      <action dev="dgraham" type="update" issue="DBUTILS-22">
+        Made QueryLoader.loadQueries() protected so subclasses can use query
+        repositories other than properties files.
+      </action>
+      <action dev="dgraham" type="update">
+        QueryRunner now calls getDataSource() internally any time it needs access
+        to its DataSource object to allow subclasses to provide different behavior.
+      </action>
+      <action dev="dgraham" type="add">
+        Added DbUtils.rollbackAndClose() and DbUtils.rollbackAndCloseQuietly().
+      </action>
+      <action dev="dgraham" type="update" issue="DBUTILS-26">
+        Call ResultSet.getTimestamp() in BeanProcessor.processColumn() if
+        the bean property is a java.sql.Timestamp.  Oracle's getObject()
+        implementation returns its own incompatible Timestamp class.
+      </action>
+      <action dev="dgraham" type="update" issue="DBUTILS-18">
+        Changed QueryRunner.fillStatement() null handling
+        to use Types.VARCHAR instead of Types.OTHER.  This works for the
+        following tested drivers: Firebird 1.5/firebirdsql 1.5RC3,
+        Oracle 9/ Thin driver, MySQL 4.0/Msql Connecttor 3.0 and mm.mysql
+        2.0.4 MaxDB 7.5, HSQLDB 1.7.1, and MS Access/ODBC Bridge.
+      </action>
+      <action dev="dgraham" type="add" issue="DBUTILS-21">
+        Added a protected QueryRunner.prepareConnection() method to
+        allow subclasses to customize the Connections retrieved from
+        the DataSource before they're used.
+      </action>
+      <action dev="dgraham" type="add">
+        Refactored bean handling from BasicRowProcessor into new
+        BeanProcessor class.  This also fixes the common problem with
+        Oracle NUMERIC fields not being set into bean properties.
+      </action>
+      <action dev="dgraham" type="add" issue="DBUTILS-13">
+        Added QueryRunner.batch() methods for batch updates.
+      </action>
+      <action dev="dgraham" type="add" issue="DBUTILS-11">
+        Added new ResultSetHandler implementation, ColumnListHandler, that
+        converts one ResultSet column into a List of Objects.
+      </action>
+    </release>
+    <release version="1.0" date="2003-11-10" description="First release of DbUtils">
+      <action type="add">
+        This is the first release of the Commons DbUtils package.  DbUtils
+        is a small set of classes designed to make working with JDBC easier.
+      </action>
+      <action type="add">
+        QueryRunner class with ResultSetHandler interface allow you to easily query or
+        update a database and handle the ResultSet.  Several useful implementations
+        of the ResultSetHandler interface are located in the
+        org.apache.commons.dbutils.handlers.* package.
+      </action>
+      <action type="add">
+        ResultSet wrappers that decorate ResultSets with specialized
+        behavior.  See the classes in the org.apache.commons.dbutils.wrappers.*
+        package for details.
+      </action>
+      <action type="add">
+        Dynamic JDBC API interface implementations via the standard
+        java.lang.reflect.Proxy class.  This allows you to implement JDBC
+        interfaces such as ResultSet at runtime to avoid API version
+        incompatibilities.  See org.apache.commons.dbutils.ProxyFactory
+        for details.
+      </action>
+    </release>
+  </body>
+</document>
diff --git a/src/main/java/org/apache/commons/dbutils/AbstractQueryRunner.java b/src/main/java/org/apache/commons/dbutils/AbstractQueryRunner.java
index 1e93975..df065cb 100644
--- a/src/main/java/org/apache/commons/dbutils/AbstractQueryRunner.java
+++ b/src/main/java/org/apache/commons/dbutils/AbstractQueryRunner.java
@@ -1,667 +1,667 @@
-/*

- * 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.dbutils;

-

-import java.beans.IntrospectionException;

-import java.beans.Introspector;

-import java.beans.PropertyDescriptor;

-import java.lang.reflect.InvocationTargetException;

-import java.lang.reflect.Method;

-import java.sql.CallableStatement;

-import java.sql.Connection;

-import java.sql.ParameterMetaData;

-import java.sql.PreparedStatement;

-import java.sql.ResultSet;

-import java.sql.SQLException;

-import java.sql.SQLFeatureNotSupportedException;

-import java.sql.Statement;

-import java.sql.Types;

-import java.util.Arrays;

-

-import javax.sql.DataSource;

-

-/**

- * The base class for QueryRunner &amp; AsyncQueryRunner. This class is thread safe.

- *

- * @since 1.4 (mostly extracted from QueryRunner)

- */

-public abstract class AbstractQueryRunner {

-    /**

-     * Is {@link ParameterMetaData#getParameterType(int)} broken (have we tried

-     * it yet)?

-     */

-    private volatile boolean pmdKnownBroken;

-

-    /**

-     * The DataSource to retrieve connections from.

-     * @deprecated Access to this field should be through {@link #getDataSource()}.

-     */

-    @Deprecated

-    protected final DataSource ds;

-

-    /**

-     * Configuration to use when preparing statements.

-     */

-    private final StatementConfiguration stmtConfig;

-

-    /**

-     * Default constructor, sets pmdKnownBroken to false, ds to null and stmtConfig to null.

-     */

-    public AbstractQueryRunner() {

-        ds = null;

-        this.stmtConfig = null;

-    }

-

-    /**

-     * Constructor to control the use of {@code ParameterMetaData}.

-     *

-     * @param pmdKnownBroken

-     *            Some drivers don't support

-     *            {@link ParameterMetaData#getParameterType(int) }; if

-     *            {@code pmdKnownBroken} is set to true, we won't even try

-     *            it; if false, we'll try it, and if it breaks, we'll remember

-     *            not to use it again.

-     */

-    public AbstractQueryRunner(final boolean pmdKnownBroken) {

-        this.pmdKnownBroken = pmdKnownBroken;

-        ds = null;

-        this.stmtConfig = null;

-    }

-

-    /**

-     * Constructor to provide a {@code DataSource}. Methods that do not

-     * take a {@code Connection} parameter will retrieve connections from

-     * this {@code DataSource}.

-     *

-     * @param ds

-     *            The {@code DataSource} to retrieve connections from.

-     */

-    public AbstractQueryRunner(final DataSource ds) {

-        this.ds = ds;

-        this.stmtConfig = null;

-    }

-

-    /**

-     * Constructor to provide a {@code DataSource} and control the use of

-     * {@code ParameterMetaData}. Methods that do not take a

-     * {@code Connection} parameter will retrieve connections from this

-     * {@code DataSource}.

-     *

-     * @param ds

-     *            The {@code DataSource} to retrieve connections from.

-     * @param pmdKnownBroken

-     *            Some drivers don't support

-     *            {@link ParameterMetaData#getParameterType(int) }; if

-     *            {@code pmdKnownBroken} is set to true, we won't even try

-     *            it; if false, we'll try it, and if it breaks, we'll remember

-     *            not to use it again.

-     */

-    public AbstractQueryRunner(final DataSource ds, final boolean pmdKnownBroken) {

-        this.pmdKnownBroken = pmdKnownBroken;

-        this.ds = ds;

-        this.stmtConfig = null;

-    }

-

-    /**

-     * Constructor for QueryRunner that takes a {@code DataSource}, a {@code StatementConfiguration}, and

-     * controls the use of {@code ParameterMetaData}.  Methods that do not take a {@code Connection} parameter

-     * will retrieve connections from this {@code DataSource}.

-     *

-     * @param ds The {@code DataSource} to retrieve connections from.

-     * @param pmdKnownBroken Some drivers don't support {@link java.sql.ParameterMetaData#getParameterType(int) };

-     * if {@code pmdKnownBroken} is set to true, we won't even try it; if false, we'll try it,

-     * and if it breaks, we'll remember not to use it again.

-     * @param stmtConfig The configuration to apply to statements when they are prepared.

-     */

-    public AbstractQueryRunner(final DataSource ds, final boolean pmdKnownBroken, final StatementConfiguration stmtConfig) {

-        this.pmdKnownBroken = pmdKnownBroken;

-        this.ds = ds;

-        this.stmtConfig = stmtConfig;

-    }

-

-    /**

-     * Constructor for QueryRunner that takes a {@code DataSource} to use and a {@code StatementConfiguration}.

-     *

-     * Methods that do not take a {@code Connection} parameter will retrieve connections from this

-     * {@code DataSource}.

-     *

-     * @param ds The {@code DataSource} to retrieve connections from.

-     * @param stmtConfig The configuration to apply to statements when they are prepared.

-     */

-    public AbstractQueryRunner(final DataSource ds, final StatementConfiguration stmtConfig) {

-        this.ds = ds;

-        this.stmtConfig = stmtConfig;

-    }

-

-    /**

-     * Constructor for QueryRunner that takes a {@code StatementConfiguration} to configure statements when

-     * preparing them.

-     *

-     * @param stmtConfig The configuration to apply to statements when they are prepared.

-     */

-    public AbstractQueryRunner(final StatementConfiguration stmtConfig) {

-        this.ds = null;

-        this.stmtConfig = stmtConfig;

-    }

-

-    /**

-     * Close a {@code Connection}. This implementation avoids closing if

-     * null and does <strong>not</strong> suppress any exceptions. Subclasses

-     * can override to provide special handling like logging.

-     *

-     * @param conn

-     *            Connection to close

-     * @throws SQLException

-     *             if a database access error occurs

-     * @since 1.1

-     */

-    protected void close(final Connection conn) throws SQLException {

-        DbUtils.close(conn);

-    }

-

-    /**

-     * Close a {@code ResultSet}. This implementation avoids closing if

-     * null and does <strong>not</strong> suppress any exceptions. Subclasses

-     * can override to provide special handling like logging.

-     *

-     * @param resultSet

-     *            ResultSet to close

-     * @throws SQLException

-     *             if a database access error occurs

-     * @since 1.1

-     */

-    protected void close(final ResultSet resultSet) throws SQLException {

-        DbUtils.close(resultSet);

-    }

-

-    /**

-     * Close a {@code Statement}. This implementation avoids closing if

-     * null and does <strong>not</strong> suppress any exceptions. Subclasses

-     * can override to provide special handling like logging.

-     *

-     * @param stmt

-     *            Statement to close

-     * @throws SQLException

-     *             if a database access error occurs

-     * @since 1.1

-     */

-    protected void close(final Statement stmt) throws SQLException {

-        DbUtils.close(stmt);

-    }

-

-    /**

-     * Calls {@link DbUtils#closeQuietly(Connection)}.

-     *

-     * @param conn Connection to close.

-     * @since 1.8.0

-     */

-    protected void closeQuietly(final Connection conn) {

-        DbUtils.closeQuietly(conn);

-    }

-

-    /**

-     * Calls {@link DbUtils#closeQuietly(ResultSet)}.

-     *

-     * @param resultSet ResultSet to close.

-     * @since 1.8.0

-     */

-    protected void closeQuietly(final ResultSet resultSet) {

-        DbUtils.closeQuietly(resultSet);

-    }

-

-    /**

-     * Calls {@link DbUtils#closeQuietly(Statement)}.

-     *

-     * @param statement ResultSet to close.

-     * @since 1.8.0

-     */

-    protected void closeQuietly(final Statement statement) {

-        DbUtils.closeQuietly(statement);

-    }

-

-    private void configureStatement(final Statement stmt) throws SQLException {

-

-        if (stmtConfig != null) {

-            if (stmtConfig.isFetchDirectionSet()) {

-                stmt.setFetchDirection(stmtConfig.getFetchDirection());

-            }

-

-            if (stmtConfig.isFetchSizeSet()) {

-                stmt.setFetchSize(stmtConfig.getFetchSize());

-            }

-

-            if (stmtConfig.isMaxFieldSizeSet()) {

-                stmt.setMaxFieldSize(stmtConfig.getMaxFieldSize());

-            }

-

-            if (stmtConfig.isMaxRowsSet()) {

-                stmt.setMaxRows(stmtConfig.getMaxRows());

-            }

-

-            if (stmtConfig.isQueryTimeoutSet()) {

-                stmt.setQueryTimeout(stmtConfig.getQueryTimeout());

-            }

-        }

-    }

-

-    /**

-     * Fill the {@code PreparedStatement} replacement parameters with the

-     * given objects.

-     *

-     * @param stmt

-     *            PreparedStatement to fill

-     * @param params

-     *            Query replacement parameters; {@code null} is a valid

-     *            value to pass in.

-     * @throws SQLException

-     *             if a database access error occurs

-     */

-    public void fillStatement(final PreparedStatement stmt, final Object... params) throws SQLException {

-        ParameterMetaData pmd = null;

-        if (!pmdKnownBroken) {

-            try {

-                pmd = getParameterMetaData(stmt);

-                if (pmd == null) { // can be returned by implementations that don't support the method

-                    pmdKnownBroken = true;

-                }

-            } catch (final SQLFeatureNotSupportedException ex) {

-                pmdKnownBroken = true;

-            }

-            // TODO see DBUTILS-117: would it make sense to catch any other SQLEx types here?

-        }

-        fillStatement(stmt, pmd, params);

-    }

-

-    /**

-     * Fill the {@code PreparedStatement} replacement parameters with the

-     * given objects, and prefetched parameter metadata.

-     *

-     * @param stmt

-     *            PreparedStatement to fill

-     * @param pmd

-     *            Prefetched parameter metadata

-     * @param params

-     *            Query replacement parameters; {@code null} is a valid

-     *            value to pass in.

-     * @throws SQLException

-     *             if a database access error occurs

-     */

-    public void fillStatement(final PreparedStatement stmt, final ParameterMetaData pmd, final Object... params)

-            throws SQLException {

-

-        // check the parameter count, if we can

-        if (!pmdKnownBroken && pmd != null) {

-            final int stmtCount = pmd.getParameterCount();

-            final int paramsCount = params == null ? 0 : params.length;

-

-            if (stmtCount != paramsCount) {

-                throw new SQLException("Wrong number of parameters: expected "

-                        + stmtCount + ", was given " + paramsCount);

-            }

-        }

-

-        // nothing to do here

-        if (params == null) {

-            return;

-        }

-

-        CallableStatement call = null;

-        if (stmt instanceof CallableStatement) {

-            call = (CallableStatement) stmt;

-        }

-

-        for (int i = 0; i < params.length; i++) {

-            if (params[i] != null) {

-                if (call != null && params[i] instanceof OutParameter) {

-                    ((OutParameter<?>) params[i]).register(call, i + 1);

-                } else {

-                    stmt.setObject(i + 1, params[i]);

-                }

-            } else {

-                // VARCHAR works with many drivers regardless

-                // of the actual column type. Oddly, NULL and

-                // OTHER don't work with Oracle's drivers.

-                int sqlType = Types.VARCHAR;

-                if (!pmdKnownBroken) {

-                    // TODO see DBUTILS-117: does it make sense to catch SQLEx here?

-                    try {

-                        /*

-                         * It's not possible for pmdKnownBroken to change from true to false, (once true, always true) so pmd cannot be null here.

-                         */

-                        sqlType = pmd.getParameterType(i + 1);

-                    } catch (final SQLException e) {

-                        pmdKnownBroken = true;

-                    }

-                }

-                stmt.setNull(i + 1, sqlType);

-            }

-        }

-    }

-

-    /**

-     * Fill the {@code PreparedStatement} replacement parameters with the

-     * given object's bean property values.

-     *

-     * @param stmt

-     *            PreparedStatement to fill

-     * @param bean

-     *            a JavaBean object

-     * @param properties

-     *            an ordered array of properties; this gives the order to insert

-     *            values in the statement

-     * @throws SQLException

-     *             if a database access error occurs

-     */

-    public void fillStatementWithBean(final PreparedStatement stmt, final Object bean,

-            final PropertyDescriptor[] properties) throws SQLException {

-        final Object[] params = new Object[properties.length];

-        for (int i = 0; i < properties.length; i++) {

-            final PropertyDescriptor property = properties[i];

-            Object value = null;

-            final Method method = property.getReadMethod();

-            if (method == null) {

-                throw new IllegalArgumentException("No read method for bean property " + bean.getClass() + " " + property.getName());

-            }

-            try {

-                value = method.invoke(bean);

-            } catch (final IllegalArgumentException e) {

-                throw new IllegalArgumentException("Couldn't invoke method with 0 arguments: " + method, e);

-            } catch (final InvocationTargetException | IllegalAccessException e) {

-                throw new IllegalArgumentException("Couldn't invoke method: " + method, e);

-            }

-            params[i] = value;

-        }

-        fillStatement(stmt, params);

-    }

-

-    /**

-     * Fill the {@code PreparedStatement} replacement parameters with the

-     * given object's bean property values.

-     *

-     * @param stmt

-     *            PreparedStatement to fill

-     * @param bean

-     *            A JavaBean object

-     * @param propertyNames

-     *            An ordered array of property names (these should match the

-     *            getters/setters); this gives the order to insert values in the

-     *            statement

-     * @throws SQLException

-     *             If a database access error occurs

-     */

-    public void fillStatementWithBean(final PreparedStatement stmt, final Object bean,

-            final String... propertyNames) throws SQLException {

-        PropertyDescriptor[] descriptors;

-        try {

-            descriptors = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();

-        } catch (final IntrospectionException e) {

-            throw new IllegalArgumentException("Couldn't introspect bean " + bean.getClass().toString(), e);

-        }

-        final PropertyDescriptor[] sorted = new PropertyDescriptor[propertyNames.length];

-        for (int i = 0; i < propertyNames.length; i++) {

-            final String propertyName = propertyNames[i];

-            if (propertyName == null) {

-                throw new NullPointerException("propertyName can't be null: " + i);

-            }

-            boolean found = false;

-            for (final PropertyDescriptor descriptor : descriptors) {

-                if (propertyName.equals(descriptor.getName())) {

-                    sorted[i] = descriptor;

-                    found = true;

-                    break;

-                }

-            }

-            if (!found) {

-                throw new IllegalStateException("Couldn't find bean property: " + bean.getClass() + " " + propertyName);

-            }

-        }

-        fillStatementWithBean(stmt, bean, sorted);

-    }

-

-    /**

-     * Returns the {@code DataSource} this runner is using.

-     * {@code QueryRunner} methods always call this method to get the

-     * {@code DataSource} so subclasses can provide specialized behavior.

-     *

-     * @return DataSource the runner is using

-     */

-    public DataSource getDataSource() {

-        return this.ds;

-    }

-

-    /**

-     * Gets the {@code ParameterMetaData} of the prepared statement, if the {@code pmdKnownBroken}

-     * is set to false.

-     *

-     * @param stmt

-     *            PreparedStatement of which to query the metadata of parameters

-     * @return the metadata of parameters

-     * @throws SQLException

-     *            if a database access error occurs

-     */

-    public ParameterMetaData getParameterMetaData(final PreparedStatement stmt) throws SQLException {

-        ParameterMetaData pmd = null;

-        if (!pmdKnownBroken) {

-            try {

-                pmd = stmt.getParameterMetaData();

-            } catch (final SQLFeatureNotSupportedException ex) {

-                pmdKnownBroken = true;

-            }

-        }

-        return pmd;

-    }

-

-    /**

-     * Some drivers don't support

-     * {@link ParameterMetaData#getParameterType(int) }; if

-     * {@code pmdKnownBroken} is set to true, we won't even try it; if

-     * false, we'll try it, and if it breaks, we'll remember not to use it

-     * again.

-     *

-     * @return the flag to skip (or not)

-     *         {@link ParameterMetaData#getParameterType(int) }

-     * @since 1.4

-     */

-    public boolean isPmdKnownBroken() {

-        return pmdKnownBroken;

-    }

-

-    /**

-     * Factory method that creates and initializes a

-     * {@code CallableStatement} object for the given SQL.

-     * {@code QueryRunner} methods always call this method to prepare

-     * callable statements for them. Subclasses can override this method to

-     * provide special CallableStatement configuration if needed. This

-     * implementation simply calls {@code conn.prepareCall(sql)}.

-     *

-     * @param conn

-     *            The {@code Connection} used to create the

-     *            {@code CallableStatement}

-     * @param sql

-     *            The SQL statement to prepare.

-     * @return An initialized {@code CallableStatement}.

-     * @throws SQLException

-     *             if a database access error occurs

-     */

-    protected CallableStatement prepareCall(final Connection conn, final String sql)

-            throws SQLException {

-

-        return conn.prepareCall(sql);

-    }

-

-    /**

-     * Factory method that creates and initializes a {@code Connection}

-     * object. {@code QueryRunner} methods always call this method to

-     * retrieve connections from its DataSource. Subclasses can override this

-     * method to provide special {@code Connection} configuration if

-     * needed. This implementation simply calls {@code ds.getConnection()}.

-     *

-     * @return An initialized {@code Connection}.

-     * @throws SQLException

-     *             if a database access error occurs

-     * @since 1.1

-     */

-    protected Connection prepareConnection() throws SQLException {

-        if (getDataSource() == null) {

-            throw new SQLException(

-                    "QueryRunner requires a DataSource to be "

-                            + "invoked in this way, or a Connection should be passed in");

-        }

-        return getDataSource().getConnection();

-    }

-

-    /**

-     * Factory method that creates and initializes a

-     * {@code PreparedStatement} object for the given SQL.

-     * {@code QueryRunner} methods always call this method to prepare

-     * statements for them. Subclasses can override this method to provide

-     * special PreparedStatement configuration if needed. This implementation

-     * simply calls {@code conn.prepareStatement(sql)}.

-     *

-     * @param conn

-     *            The {@code Connection} used to create the

-     *            {@code PreparedStatement}

-     * @param sql

-     *            The SQL statement to prepare.

-     * @return An initialized {@code PreparedStatement}.

-     * @throws SQLException

-     *             if a database access error occurs

-     */

-    protected PreparedStatement prepareStatement(final Connection conn, final String sql)

-            throws SQLException {

-

-        @SuppressWarnings("resource")

-        final

-        PreparedStatement ps = conn.prepareStatement(sql);

-        try {

-            configureStatement(ps);

-        } catch (final SQLException e) {

-            ps.close();

-            throw e;

-        }

-        return ps;

-    }

-

-    /**

-     * Factory method that creates and initializes a

-     * {@code PreparedStatement} object for the given SQL.

-     * {@code QueryRunner} methods always call this method to prepare

-     * statements for them. Subclasses can override this method to provide

-     * special PreparedStatement configuration if needed. This implementation

-     * simply calls {@code conn.prepareStatement(sql, returnedKeys)}

-     * which will result in the ability to retrieve the automatically-generated

-     * keys from an auto_increment column.

-     *

-     * @param conn

-     *            The {@code Connection} used to create the

-     *            {@code PreparedStatement}

-     * @param sql

-     *            The SQL statement to prepare.

-     * @param returnedKeys

-     *            Flag indicating whether to return generated keys or not.

-     *

-     * @return An initialized {@code PreparedStatement}.

-     * @throws SQLException

-     *             if a database access error occurs

-     * @since 1.6

-     */

-    protected PreparedStatement prepareStatement(final Connection conn, final String sql, final int returnedKeys)

-            throws SQLException {

-

-        @SuppressWarnings("resource")

-        final

-        PreparedStatement ps = conn.prepareStatement(sql, returnedKeys);

-        try {

-            configureStatement(ps);

-        } catch (final SQLException e) {

-            ps.close();

-            throw e;

-        }

-        return ps;

-    }

-

-    /**

-     * Throws a new exception with a more informative error message.

-     *

-     * @param cause

-     *            The original exception that will be chained to the new

-     *            exception when it's rethrown.

-     *

-     * @param sql

-     *            The query that was executing when the exception happened.

-     *

-     * @param params

-     *            The query replacement parameters; {@code null} is a valid

-     *            value to pass in.

-     *

-     * @throws SQLException

-     *             if a database access error occurs

-     */

-    protected void rethrow(final SQLException cause, final String sql, final Object... params)

-            throws SQLException {

-

-        String causeMessage = cause.getMessage();

-        if (causeMessage == null) {

-            causeMessage = "";

-        }

-        final StringBuilder msg = new StringBuilder(causeMessage);

-

-        msg.append(" Query: ");

-        msg.append(sql);

-        msg.append(" Parameters: ");

-

-        if (params == null) {

-            msg.append("[]");

-        } else {

-            msg.append(Arrays.deepToString(params));

-        }

-

-        final SQLException e = new SQLException(msg.toString(), cause.getSQLState(),

-                cause.getErrorCode());

-        e.setNextException(cause);

-

-        throw e;

-    }

-

-    /**

-     * Wrap the {@code ResultSet} in a decorator before processing it. This

-     * implementation returns the {@code ResultSet} it is given without any

-     * decoration.

-     *

-     * <p>

-     * Often, the implementation of this method can be done in an anonymous

-     * inner class like this:

-     * </p>

-     *

-     * <pre>

-     * QueryRunner run = new QueryRunner() {

-     *     protected ResultSet wrap(ResultSet rs) {

-     *         return StringTrimmedResultSet.wrap(rs);

-     *     }

-     * };

-     * </pre>

-     *

-     * @param rs

-     *            The {@code ResultSet} to decorate; never

-     *            {@code null}.

-     * @return The {@code ResultSet} wrapped in some decorator.

-     */

-    protected ResultSet wrap(final ResultSet rs) {

-        return rs;

-    }

-

-}

+/*
+ * 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.dbutils;
+
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.ParameterMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.Statement;
+import java.sql.Types;
+import java.util.Arrays;
+
+import javax.sql.DataSource;
+
+/**
+ * The base class for QueryRunner &amp; AsyncQueryRunner. This class is thread safe.
+ *
+ * @since 1.4 (mostly extracted from QueryRunner)
+ */
+public abstract class AbstractQueryRunner {
+    /**
+     * Is {@link ParameterMetaData#getParameterType(int)} broken (have we tried
+     * it yet)?
+     */
+    private volatile boolean pmdKnownBroken;
+
+    /**
+     * The DataSource to retrieve connections from.
+     * @deprecated Access to this field should be through {@link #getDataSource()}.
+     */
+    @Deprecated
+    protected final DataSource ds;
+
+    /**
+     * Configuration to use when preparing statements.
+     */
+    private final StatementConfiguration stmtConfig;
+
+    /**
+     * Default constructor, sets pmdKnownBroken to false, ds to null and stmtConfig to null.
+     */
+    public AbstractQueryRunner() {
+        ds = null;
+        this.stmtConfig = null;
+    }
+
+    /**
+     * Constructor to control the use of {@code ParameterMetaData}.
+     *
+     * @param pmdKnownBroken
+     *            Some drivers don't support
+     *            {@link ParameterMetaData#getParameterType(int) }; if
+     *            {@code pmdKnownBroken} is set to true, we won't even try
+     *            it; if false, we'll try it, and if it breaks, we'll remember
+     *            not to use it again.
+     */
+    public AbstractQueryRunner(final boolean pmdKnownBroken) {
+        this.pmdKnownBroken = pmdKnownBroken;
+        ds = null;
+        this.stmtConfig = null;
+    }
+
+    /**
+     * Constructor to provide a {@code DataSource}. Methods that do not
+     * take a {@code Connection} parameter will retrieve connections from
+     * this {@code DataSource}.
+     *
+     * @param ds
+     *            The {@code DataSource} to retrieve connections from.
+     */
+    public AbstractQueryRunner(final DataSource ds) {
+        this.ds = ds;
+        this.stmtConfig = null;
+    }
+
+    /**
+     * Constructor to provide a {@code DataSource} and control the use of
+     * {@code ParameterMetaData}. Methods that do not take a
+     * {@code Connection} parameter will retrieve connections from this
+     * {@code DataSource}.
+     *
+     * @param ds
+     *            The {@code DataSource} to retrieve connections from.
+     * @param pmdKnownBroken
+     *            Some drivers don't support
+     *            {@link ParameterMetaData#getParameterType(int) }; if
+     *            {@code pmdKnownBroken} is set to true, we won't even try
+     *            it; if false, we'll try it, and if it breaks, we'll remember
+     *            not to use it again.
+     */
+    public AbstractQueryRunner(final DataSource ds, final boolean pmdKnownBroken) {
+        this.pmdKnownBroken = pmdKnownBroken;
+        this.ds = ds;
+        this.stmtConfig = null;
+    }
+
+    /**
+     * Constructor for QueryRunner that takes a {@code DataSource}, a {@code StatementConfiguration}, and
+     * controls the use of {@code ParameterMetaData}.  Methods that do not take a {@code Connection} parameter
+     * will retrieve connections from this {@code DataSource}.
+     *
+     * @param ds The {@code DataSource} to retrieve connections from.
+     * @param pmdKnownBroken Some drivers don't support {@link java.sql.ParameterMetaData#getParameterType(int) };
+     * if {@code pmdKnownBroken} is set to true, we won't even try it; if false, we'll try it,
+     * and if it breaks, we'll remember not to use it again.
+     * @param stmtConfig The configuration to apply to statements when they are prepared.
+     */
+    public AbstractQueryRunner(final DataSource ds, final boolean pmdKnownBroken, final StatementConfiguration stmtConfig) {
+        this.pmdKnownBroken = pmdKnownBroken;
+        this.ds = ds;
+        this.stmtConfig = stmtConfig;
+    }
+
+    /**
+     * Constructor for QueryRunner that takes a {@code DataSource} to use and a {@code StatementConfiguration}.
+     *
+     * Methods that do not take a {@code Connection} parameter will retrieve connections from this
+     * {@code DataSource}.
+     *
+     * @param ds The {@code DataSource} to retrieve connections from.
+     * @param stmtConfig The configuration to apply to statements when they are prepared.
+     */
+    public AbstractQueryRunner(final DataSource ds, final StatementConfiguration stmtConfig) {
+        this.ds = ds;
+        this.stmtConfig = stmtConfig;
+    }
+
+    /**
+     * Constructor for QueryRunner that takes a {@code StatementConfiguration} to configure statements when
+     * preparing them.
+     *
+     * @param stmtConfig The configuration to apply to statements when they are prepared.
+     */
+    public AbstractQueryRunner(final StatementConfiguration stmtConfig) {
+        this.ds = null;
+        this.stmtConfig = stmtConfig;
+    }
+
+    /**
+     * Close a {@code Connection}. This implementation avoids closing if
+     * null and does <strong>not</strong> suppress any exceptions. Subclasses
+     * can override to provide special handling like logging.
+     *
+     * @param conn
+     *            Connection to close
+     * @throws SQLException
+     *             if a database access error occurs
+     * @since 1.1
+     */
+    protected void close(final Connection conn) throws SQLException {
+        DbUtils.close(conn);
+    }
+
+    /**
+     * Close a {@code ResultSet}. This implementation avoids closing if
+     * null and does <strong>not</strong> suppress any exceptions. Subclasses
+     * can override to provide special handling like logging.
+     *
+     * @param resultSet
+     *            ResultSet to close
+     * @throws SQLException
+     *             if a database access error occurs
+     * @since 1.1
+     */
+    protected void close(final ResultSet resultSet) throws SQLException {
+        DbUtils.close(resultSet);
+    }
+
+    /**
+     * Close a {@code Statement}. This implementation avoids closing if
+     * null and does <strong>not</strong> suppress any exceptions. Subclasses
+     * can override to provide special handling like logging.
+     *
+     * @param stmt
+     *            Statement to close
+     * @throws SQLException
+     *             if a database access error occurs
+     * @since 1.1
+     */
+    protected void close(final Statement stmt) throws SQLException {
+        DbUtils.close(stmt);
+    }
+
+    /**
+     * Calls {@link DbUtils#closeQuietly(Connection)}.
+     *
+     * @param conn Connection to close.
+     * @since 1.8.0
+     */
+    protected void closeQuietly(final Connection conn) {
+        DbUtils.closeQuietly(conn);
+    }
+
+    /**
+     * Calls {@link DbUtils#closeQuietly(ResultSet)}.
+     *
+     * @param resultSet ResultSet to close.
+     * @since 1.8.0
+     */
+    protected void closeQuietly(final ResultSet resultSet) {
+        DbUtils.closeQuietly(resultSet);
+    }
+
+    /**
+     * Calls {@link DbUtils#closeQuietly(Statement)}.
+     *
+     * @param statement ResultSet to close.
+     * @since 1.8.0
+     */
+    protected void closeQuietly(final Statement statement) {
+        DbUtils.closeQuietly(statement);
+    }
+
+    private void configureStatement(final Statement stmt) throws SQLException {
+
+        if (stmtConfig != null) {
+            if (stmtConfig.isFetchDirectionSet()) {
+                stmt.setFetchDirection(stmtConfig.getFetchDirection());
+            }
+
+            if (stmtConfig.isFetchSizeSet()) {
+                stmt.setFetchSize(stmtConfig.getFetchSize());
+            }
+
+            if (stmtConfig.isMaxFieldSizeSet()) {
+                stmt.setMaxFieldSize(stmtConfig.getMaxFieldSize());
+            }
+
+            if (stmtConfig.isMaxRowsSet()) {
+                stmt.setMaxRows(stmtConfig.getMaxRows());
+            }
+
+            if (stmtConfig.isQueryTimeoutSet()) {
+                stmt.setQueryTimeout(stmtConfig.getQueryTimeout());
+            }
+        }
+    }
+
+    /**
+     * Fill the {@code PreparedStatement} replacement parameters with the
+     * given objects.
+     *
+     * @param stmt
+     *            PreparedStatement to fill
+     * @param params
+     *            Query replacement parameters; {@code null} is a valid
+     *            value to pass in.
+     * @throws SQLException
+     *             if a database access error occurs
+     */
+    public void fillStatement(final PreparedStatement stmt, final Object... params) throws SQLException {
+        ParameterMetaData pmd = null;
+        if (!pmdKnownBroken) {
+            try {
+                pmd = getParameterMetaData(stmt);
+                if (pmd == null) { // can be returned by implementations that don't support the method
+                    pmdKnownBroken = true;
+                }
+            } catch (final SQLFeatureNotSupportedException ex) {
+                pmdKnownBroken = true;
+            }
+            // TODO see DBUTILS-117: would it make sense to catch any other SQLEx types here?
+        }
+        fillStatement(stmt, pmd, params);
+    }
+
+    /**
+     * Fill the {@code PreparedStatement} replacement parameters with the
+     * given objects, and prefetched parameter metadata.
+     *
+     * @param stmt
+     *            PreparedStatement to fill
+     * @param pmd
+     *            Prefetched parameter metadata
+     * @param params
+     *            Query replacement parameters; {@code null} is a valid
+     *            value to pass in.
+     * @throws SQLException
+     *             if a database access error occurs
+     */
+    public void fillStatement(final PreparedStatement stmt, final ParameterMetaData pmd, final Object... params)
+            throws SQLException {
+
+        // check the parameter count, if we can
+        if (!pmdKnownBroken && pmd != null) {
+            final int stmtCount = pmd.getParameterCount();
+            final int paramsCount = params == null ? 0 : params.length;
+
+            if (stmtCount != paramsCount) {
+                throw new SQLException("Wrong number of parameters: expected "
+                        + stmtCount + ", was given " + paramsCount);
+            }
+        }
+
+        // nothing to do here
+        if (params == null) {
+            return;
+        }
+
+        CallableStatement call = null;
+        if (stmt instanceof CallableStatement) {
+            call = (CallableStatement) stmt;
+        }
+
+        for (int i = 0; i < params.length; i++) {
+            if (params[i] != null) {
+                if (call != null && params[i] instanceof OutParameter) {
+                    ((OutParameter<?>) params[i]).register(call, i + 1);
+                } else {
+                    stmt.setObject(i + 1, params[i]);
+                }
+            } else {
+                // VARCHAR works with many drivers regardless
+                // of the actual column type. Oddly, NULL and
+                // OTHER don't work with Oracle's drivers.
+                int sqlType = Types.VARCHAR;
+                if (!pmdKnownBroken) {
+                    // TODO see DBUTILS-117: does it make sense to catch SQLEx here?
+                    try {
+                        /*
+                         * It's not possible for pmdKnownBroken to change from true to false, (once true, always true) so pmd cannot be null here.
+                         */
+                        sqlType = pmd.getParameterType(i + 1);
+                    } catch (final SQLException e) {
+                        pmdKnownBroken = true;
+                    }
+                }
+                stmt.setNull(i + 1, sqlType);
+            }
+        }
+    }
+
+    /**
+     * Fill the {@code PreparedStatement} replacement parameters with the
+     * given object's bean property values.
+     *
+     * @param stmt
+     *            PreparedStatement to fill
+     * @param bean
+     *            a JavaBean object
+     * @param properties
+     *            an ordered array of properties; this gives the order to insert
+     *            values in the statement
+     * @throws SQLException
+     *             if a database access error occurs
+     */
+    public void fillStatementWithBean(final PreparedStatement stmt, final Object bean,
+            final PropertyDescriptor[] properties) throws SQLException {
+        final Object[] params = new Object[properties.length];
+        for (int i = 0; i < properties.length; i++) {
+            final PropertyDescriptor property = properties[i];
+            Object value = null;
+            final Method method = property.getReadMethod();
+            if (method == null) {
+                throw new IllegalArgumentException("No read method for bean property " + bean.getClass() + " " + property.getName());
+            }
+            try {
+                value = method.invoke(bean);
+            } catch (final IllegalArgumentException e) {
+                throw new IllegalArgumentException("Couldn't invoke method with 0 arguments: " + method, e);
+            } catch (final InvocationTargetException | IllegalAccessException e) {
+                throw new IllegalArgumentException("Couldn't invoke method: " + method, e);
+            }
+            params[i] = value;
+        }
+        fillStatement(stmt, params);
+    }
+
+    /**
+     * Fill the {@code PreparedStatement} replacement parameters with the
+     * given object's bean property values.
+     *
+     * @param stmt
+     *            PreparedStatement to fill
+     * @param bean
+     *            A JavaBean object
+     * @param propertyNames
+     *            An ordered array of property names (these should match the
+     *            getters/setters); this gives the order to insert values in the
+     *            statement
+     * @throws SQLException
+     *             If a database access error occurs
+     */
+    public void fillStatementWithBean(final PreparedStatement stmt, final Object bean,
+            final String... propertyNames) throws SQLException {
+        PropertyDescriptor[] descriptors;
+        try {
+            descriptors = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();
+        } catch (final IntrospectionException e) {
+            throw new IllegalArgumentException("Couldn't introspect bean " + bean.getClass().toString(), e);
+        }
+        final PropertyDescriptor[] sorted = new PropertyDescriptor[propertyNames.length];
+        for (int i = 0; i < propertyNames.length; i++) {
+            final String propertyName = propertyNames[i];
+            if (propertyName == null) {
+                throw new NullPointerException("propertyName can't be null: " + i);
+            }
+            boolean found = false;
+            for (final PropertyDescriptor descriptor : descriptors) {
+                if (propertyName.equals(descriptor.getName())) {
+                    sorted[i] = descriptor;
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                throw new IllegalStateException("Couldn't find bean property: " + bean.getClass() + " " + propertyName);
+            }
+        }
+        fillStatementWithBean(stmt, bean, sorted);
+    }
+
+    /**
+     * Returns the {@code DataSource} this runner is using.
+     * {@code QueryRunner} methods always call this method to get the
+     * {@code DataSource} so subclasses can provide specialized behavior.
+     *
+     * @return DataSource the runner is using
+     */
+    public DataSource getDataSource() {
+        return this.ds;
+    }
+
+    /**
+     * Gets the {@code ParameterMetaData} of the prepared statement, if the {@code pmdKnownBroken}
+     * is set to false.
+     *
+     * @param stmt
+     *            PreparedStatement of which to query the metadata of parameters
+     * @return the metadata of parameters
+     * @throws SQLException
+     *            if a database access error occurs
+     */
+    public ParameterMetaData getParameterMetaData(final PreparedStatement stmt) throws SQLException {
+        ParameterMetaData pmd = null;
+        if (!pmdKnownBroken) {
+            try {
+                pmd = stmt.getParameterMetaData();
+            } catch (final SQLFeatureNotSupportedException ex) {
+                pmdKnownBroken = true;
+            }
+        }
+        return pmd;
+    }
+
+    /**
+     * Some drivers don't support
+     * {@link ParameterMetaData#getParameterType(int) }; if
+     * {@code pmdKnownBroken} is set to true, we won't even try it; if
+     * false, we'll try it, and if it breaks, we'll remember not to use it
+     * again.
+     *
+     * @return the flag to skip (or not)
+     *         {@link ParameterMetaData#getParameterType(int) }
+     * @since 1.4
+     */
+    public boolean isPmdKnownBroken() {
+        return pmdKnownBroken;
+    }
+
+    /**
+     * Factory method that creates and initializes a
+     * {@code CallableStatement} object for the given SQL.
+     * {@code QueryRunner} methods always call this method to prepare
+     * callable statements for them. Subclasses can override this method to
+     * provide special CallableStatement configuration if needed. This
+     * implementation simply calls {@code conn.prepareCall(sql)}.
+     *
+     * @param conn
+     *            The {@code Connection} used to create the
+     *            {@code CallableStatement}
+     * @param sql
+     *            The SQL statement to prepare.
+     * @return An initialized {@code CallableStatement}.
+     * @throws SQLException
+     *             if a database access error occurs
+     */
+    protected CallableStatement prepareCall(final Connection conn, final String sql)
+            throws SQLException {
+
+        return conn.prepareCall(sql);
+    }
+
+    /**
+     * Factory method that creates and initializes a {@code Connection}
+     * object. {@code QueryRunner} methods always call this method to
+     * retrieve connections from its DataSource. Subclasses can override this
+     * method to provide special {@code Connection} configuration if
+     * needed. This implementation simply calls {@code ds.getConnection()}.
+     *
+     * @return An initialized {@code Connection}.
+     * @throws SQLException
+     *             if a database access error occurs
+     * @since 1.1
+     */
+    protected Connection prepareConnection() throws SQLException {
+        if (getDataSource() == null) {
+            throw new SQLException(
+                    "QueryRunner requires a DataSource to be "
+                            + "invoked in this way, or a Connection should be passed in");
+        }
+        return getDataSource().getConnection();
+    }
+
+    /**
+     * Factory method that creates and initializes a
+     * {@code PreparedStatement} object for the given SQL.
+     * {@code QueryRunner} methods always call this method to prepare
+     * statements for them. Subclasses can override this method to provide
+     * special PreparedStatement configuration if needed. This implementation
+     * simply calls {@code conn.prepareStatement(sql)}.
+     *
+     * @param conn
+     *            The {@code Connection} used to create the
+     *            {@code PreparedStatement}
+     * @param sql
+     *            The SQL statement to prepare.
+     * @return An initialized {@code PreparedStatement}.
+     * @throws SQLException
+     *             if a database access error occurs
+     */
+    protected PreparedStatement prepareStatement(final Connection conn, final String sql)
+            throws SQLException {
+
+        @SuppressWarnings("resource")
+        final
+        PreparedStatement ps = conn.prepareStatement(sql);
+        try {
+            configureStatement(ps);
+        } catch (final SQLException e) {
+            ps.close();
+            throw e;
+        }
+        return ps;
+    }
+
+    /**
+     * Factory method that creates and initializes a
+     * {@code PreparedStatement} object for the given SQL.
+     * {@code QueryRunner} methods always call this method to prepare
+     * statements for them. Subclasses can override this method to provide
+     * special PreparedStatement configuration if needed. This implementation
+     * simply calls {@code conn.prepareStatement(sql, returnedKeys)}
+     * which will result in the ability to retrieve the automatically-generated
+     * keys from an auto_increment column.
+     *
+     * @param conn
+     *            The {@code Connection} used to create the
+     *            {@code PreparedStatement}
+     * @param sql
+     *            The SQL statement to prepare.
+     * @param returnedKeys
+     *            Flag indicating whether to return generated keys or not.
+     *
+     * @return An initialized {@code PreparedStatement}.
+     * @throws SQLException
+     *             if a database access error occurs
+     * @since 1.6
+     */
+    protected PreparedStatement prepareStatement(final Connection conn, final String sql, final int returnedKeys)
+            throws SQLException {
+
+        @SuppressWarnings("resource")
+        final
+        PreparedStatement ps = conn.prepareStatement(sql, returnedKeys);
+        try {
+            configureStatement(ps);
+        } catch (final SQLException e) {
+            ps.close();
+            throw e;
+        }
+        return ps;
+    }
+
+    /**
+     * Throws a new exception with a more informative error message.
+     *
+     * @param cause
+     *            The original exception that will be chained to the new
+     *            exception when it's rethrown.
+     *
+     * @param sql
+     *            The query that was executing when the exception happened.
+     *
+     * @param params
+     *            The query replacement parameters; {@code null} is a valid
+     *            value to pass in.
+     *
+     * @throws SQLException
+     *             if a database access error occurs
+     */
+    protected void rethrow(final SQLException cause, final String sql, final Object... params)
+            throws SQLException {
+
+        String causeMessage = cause.getMessage();
+        if (causeMessage == null) {
+            causeMessage = "";
+        }
+        final StringBuilder msg = new StringBuilder(causeMessage);
+
+        msg.append(" Query: ");
+        msg.append(sql);
+        msg.append(" Parameters: ");
+
+        if (params == null) {
+            msg.append("[]");
+        } else {
+            msg.append(Arrays.deepToString(params));
+        }
+
+        final SQLException e = new SQLException(msg.toString(), cause.getSQLState(),
+                cause.getErrorCode());
+        e.setNextException(cause);
+
+        throw e;
+    }
+
+    /**
+     * Wrap the {@code ResultSet} in a decorator before processing it. This
+     * implementation returns the {@code ResultSet} it is given without any
+     * decoration.
+     *
+     * <p>
+     * Often, the implementation of this method can be done in an anonymous
+     * inner class like this:
+     * </p>
+     *
+     * <pre>
+     * QueryRunner run = new QueryRunner() {
+     *     protected ResultSet wrap(ResultSet rs) {
+     *         return StringTrimmedResultSet.wrap(rs);
+     *     }
+     * };
+     * </pre>
+     *
+     * @param rs
+     *            The {@code ResultSet} to decorate; never
+     *            {@code null}.
+     * @return The {@code ResultSet} wrapped in some decorator.
+     */
+    protected ResultSet wrap(final ResultSet rs) {
+        return rs;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/dbutils/AsyncQueryRunner.java b/src/main/java/org/apache/commons/dbutils/AsyncQueryRunner.java
index 8ab8142..d0587c1 100644
--- a/src/main/java/org/apache/commons/dbutils/AsyncQueryRunner.java
+++ b/src/main/java/org/apache/commons/dbutils/AsyncQueryRunner.java
@@ -1,571 +1,571 @@
-/*

- * 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.dbutils;

-

-import java.sql.Connection;

-import java.sql.PreparedStatement;

-import java.sql.ResultSet;

-import java.sql.SQLException;

-import java.util.concurrent.Callable;

-import java.util.concurrent.ExecutorService;

-import java.util.concurrent.Future;

-

-import javax.sql.DataSource;

-

-/**

- * Executes SQL queries with pluggable strategies for handling

- * {@code ResultSet}s.  This class is thread safe.

- *

- * @see ResultSetHandler

- * @since 1.4

- */

-public class AsyncQueryRunner extends AbstractQueryRunner {

-

-    /**

-     * @deprecated No longer used by this class. Will be removed in a future version.

-     * Class that encapsulates the continuation for batch calls.

-     */

-    @Deprecated

-    protected class BatchCallableStatement implements Callable<int[]> {

-        private final String sql;

-        private final Object[][] params;

-        private final Connection conn;

-        private final boolean closeConn;

-        private final PreparedStatement ps;

-

-        /**

-         * Creates a new BatchCallableStatement instance.

-         *

-         * @param sql The SQL statement to execute.

-         * @param params An array of query replacement parameters.  Each row in

-         *        this array is one set of batch replacement values.

-         * @param conn The connection to use for the batch call.

-         * @param closeConn True if the connection should be closed, false otherwise.

-         * @param ps The {@link PreparedStatement} to be executed.

-         */

-        public BatchCallableStatement(final String sql, final Object[][] params, final Connection conn, final boolean closeConn, final PreparedStatement ps) {

-            this.sql = sql;

-            this.params = params.clone();

-            this.conn = conn;

-            this.closeConn = closeConn;

-            this.ps = ps;

-        }

-

-        /**

-         * The actual call to executeBatch.

-         *

-         * @return an array of update counts containing one element for each command in the batch.

-         * @throws SQLException if a database access error occurs or one of the commands sent to the database fails.

-         * @see PreparedStatement#executeBatch()

-         */

-        @Override

-        public int[] call() throws SQLException {

-            int[] ret = null;

-

-            try {

-                ret = ps.executeBatch();

-            } catch (final SQLException e) {

-                rethrow(e, sql, (Object[])params);

-            } finally {

-                close(ps);

-                if (closeConn) {

-                    close(conn);

-                }

-            }

-

-            return ret;

-        }

-    }

-    /**

-     * Class that encapsulates the continuation for query calls.

-     * @param <T> The type of the result from the call to handle.

-     */

-    protected class QueryCallableStatement<T> implements Callable<T> {

-        private final String sql;

-        private final Object[] params;

-        private final Connection conn;

-        private final boolean closeConn;

-        private final PreparedStatement ps;

-        private final ResultSetHandler<T> rsh;

-

-        /**

-         * Creates a new {@code QueryCallableStatement} instance.

-         *

-         * @param conn The connection to use for the batch call.

-         * @param closeConn True if the connection should be closed, false otherwise.

-         * @param ps The {@link PreparedStatement} to be executed.

-         * @param rsh The handler that converts the results into an object.

-         * @param sql The SQL statement to execute.

-         * @param params An array of query replacement parameters.  Each row in

-         *        this array is one set of batch replacement values.

-         */

-        public QueryCallableStatement(final Connection conn, final boolean closeConn, final PreparedStatement ps,

-                final ResultSetHandler<T> rsh, final String sql, final Object... params) {

-            this.sql = sql;

-            this.params = params;

-            this.conn = conn;

-            this.closeConn = closeConn;

-            this.ps = ps;

-            this.rsh = rsh;

-        }

-

-        /**

-         * The actual call to {@code handle()} method.

-         *

-         * @return an array of update counts containing one element for each command in the batch.

-         * @throws SQLException if a database access error occurs.

-         * @see ResultSetHandler#handle(ResultSet)

-         */

-        @Override

-        public T call() throws SQLException {

-            ResultSet resultSet = null;

-            T ret = null;

-

-            try {

-                resultSet = wrap(ps.executeQuery());

-                ret = rsh.handle(resultSet);

-            } catch (final SQLException e) {

-                rethrow(e, sql, params);

-            } finally {

-                try {

-                    close(resultSet);

-                } finally {

-                    close(ps);

-                    if (closeConn) {

-                        close(conn);

-                    }

-                }

-            }

-

-            return ret;

-        }

-

-    }

-

-    /**

-     * Class that encapsulates the continuation for update calls.

-     *

-     * @deprecated No longer used by this class. Will be removed in a future version.

-     */

-    @Deprecated

-    protected class UpdateCallableStatement implements Callable<Integer> {

-        private final String sql;

-        private final Object[] params;

-        private final Connection conn;

-        private final boolean closeConn;

-        private final PreparedStatement ps;

-

-        /**

-         * Constructs a new instance.

-         *

-         * @param conn The connection to use for the batch call.

-         * @param closeConn True if the connection should be closed, false otherwise.

-         * @param ps The {@link PreparedStatement} to be executed.

-         * @param sql The SQL statement to execute.

-         * @param params An array of query replacement parameters.  Each row in

-         *        this array is one set of batch replacement values.

-         */

-        public UpdateCallableStatement(final Connection conn, final boolean closeConn, final PreparedStatement ps, final String sql, final Object... params) {

-            this.sql = sql;

-            this.params = params;

-            this.conn = conn;

-            this.closeConn = closeConn;

-            this.ps = ps;

-        }

-

-        /**

-         * The actual call to {@code executeUpdate()} method.

-         *

-         * @return either (1) the row count for SQL Data Manipulation Language (DML) statements or

-         *                (2) 0 for SQL statements that return nothing

-         * @throws SQLException if a database access error occurs.

-         * @see PreparedStatement#executeUpdate()

-         */

-        @Override

-        public Integer call() throws SQLException {

-            int rows = 0;

-

-            try {

-                rows = ps.executeUpdate();

-            } catch (final SQLException e) {

-                rethrow(e, sql, params);

-            } finally {

-                close(ps);

-                if (closeConn) {

-                    close(conn);

-                }

-            }

-

-            return Integer.valueOf(rows);

-        }

-

-    }

-

-    private final ExecutorService executorService;

-

-    private final QueryRunner queryRunner;

-

-    /**

-     * @deprecated Use {@link #AsyncQueryRunner(ExecutorService, QueryRunner)} instead.

-     * Constructor for AsyncQueryRunner that controls the use of {@code ParameterMetaData}.

-     *

-     * @param pmdKnownBroken Some drivers don't support {@link java.sql.ParameterMetaData#getParameterType(int) };

-     * if {@code pmdKnownBroken} is set to true, we won't even try it; if false, we'll try it,

-     * and if it breaks, we'll remember not to use it again.

-     * @param executorService the {@code ExecutorService} instance used to run JDBC invocations concurrently.

-     */

-    @Deprecated

-    public AsyncQueryRunner(final boolean pmdKnownBroken, final ExecutorService executorService) {

-        this(null, pmdKnownBroken, executorService);

-    }

-

-    /**

-     * @deprecated Use {@link #AsyncQueryRunner(ExecutorService, QueryRunner)} instead.

-     * Constructor for AsyncQueryRunner that take a {@code DataSource} and controls the use of {@code ParameterMetaData}.

-     * Methods that do not take a {@code Connection} parameter will retrieve connections from this

-     * {@code DataSource}.

-     *

-     * @param ds The {@code DataSource} to retrieve connections from.

-     * @param pmdKnownBroken Some drivers don't support {@link java.sql.ParameterMetaData#getParameterType(int) };

-     * if {@code pmdKnownBroken} is set to true, we won't even try it; if false, we'll try it,

-     * and if it breaks, we'll remember not to use it again.

-     * @param executorService the {@code ExecutorService} instance used to run JDBC invocations concurrently.

-     */

-    @Deprecated

-    public AsyncQueryRunner(final DataSource ds, final boolean pmdKnownBroken, final ExecutorService executorService) {

-        super(ds, pmdKnownBroken);

-        this.executorService = executorService;

-        this.queryRunner = new QueryRunner(ds, pmdKnownBroken);

-    }

-

-    /**

-     * @deprecated Use {@link #AsyncQueryRunner(ExecutorService, QueryRunner)} instead.

-     * Constructor for AsyncQueryRunner that takes a {@code DataSource}.

-     *

-     * Methods that do not take a {@code Connection} parameter will retrieve connections from this

-     * {@code DataSource}.

-     *

-     * @param ds The {@code DataSource} to retrieve connections from.

-     * @param executorService the {@code ExecutorService} instance used to run JDBC invocations concurrently.

-     */

-    @Deprecated

-    public AsyncQueryRunner(final DataSource ds, final ExecutorService executorService) {

-        this(ds, false, executorService);

-    }

-

-    /**

-     * Constructor for AsyncQueryRunner.

-     *

-     * @param executorService the {@code ExecutorService} instance used to run JDBC invocations concurrently.

-     */

-    public AsyncQueryRunner(final ExecutorService executorService) {

-        this(null, false, executorService);

-    }

-

-    /**

-     * Constructor for AsyncQueryRunner which uses a provided ExecutorService and underlying QueryRunner.

-     *

-     * @param executorService the {@code ExecutorService} instance used to run JDBC invocations concurrently.

-     * @param queryRunner the {@code QueryRunner} instance to use for the queries.

-     * @since 1.5

-     */

-    public AsyncQueryRunner(final ExecutorService executorService, final QueryRunner queryRunner) {

-        this.executorService = executorService;

-        this.queryRunner = queryRunner;

-    }

-

-    /**

-     * Execute a batch of SQL INSERT, UPDATE, or DELETE queries.

-     *

-     * @param conn The {@code Connection} to use to run the query.  The caller is

-     * responsible for closing this Connection.

-     * @param sql The SQL to execute.

-     * @param params An array of query replacement parameters.  Each row in

-     * this array is one set of batch replacement values.

-     * @return A {@code Future} which returns the number of rows updated per statement.

-     * @throws SQLException if a database access error occurs

-     */

-    public Future<int[]> batch(final Connection conn, final String sql, final Object[][] params) throws SQLException {

-        return executorService.submit(() -> queryRunner.batch(conn, sql, params));

-    }

-

-    /**

-     * Execute a batch of SQL INSERT, UPDATE, or DELETE queries.  The

-     * {@code Connection} is retrieved from the {@code DataSource}

-     * set in the constructor.  This {@code Connection} must be in

-     * auto-commit mode or the update will not be saved.

-     *

-     * @param sql The SQL to execute.

-     * @param params An array of query replacement parameters.  Each row in

-     * this array is one set of batch replacement values.

-     * @return A {@code Future} which returns the number of rows updated per statement.

-     * @throws SQLException if a database access error occurs

-     */

-    public Future<int[]> batch(final String sql, final Object[][] params) throws SQLException {

-        return executorService.submit(() -> queryRunner.batch(sql, params));

-    }

-

-    /**

-     * Executes {@link QueryRunner#insert(Connection, String, ResultSetHandler)} asynchronously.

-     *

-     * @param <T> Return type expected

-     * @param conn {@link Connection} to use to execute the SQL statement

-     * @param sql SQL insert statement to execute

-     * @param rsh {@link ResultSetHandler} for handling the results

-     * @return {@link Future} that executes a query runner insert

-     * @see QueryRunner#insert(Connection, String, ResultSetHandler)

-     * @throws SQLException if a database access error occurs

-     * @since 1.6

-     */

-    public <T> Future<T> insert(final Connection conn, final String sql, final ResultSetHandler<T> rsh) throws SQLException {

-        return executorService.submit(() -> queryRunner.insert(conn, sql, rsh));

-    }

-

-    /**

-     * Executes {@link QueryRunner#insert(Connection, String, ResultSetHandler, Object...)} asynchronously.

-     *

-     * @param <T> Return type expected

-     * @param conn {@link Connection} to use to execute the SQL statement

-     * @param sql SQL insert statement to execute

-     * @param rsh {@link ResultSetHandler} for handling the results

-     * @param params Parameter values for substitution in the SQL statement

-     * @return {@link Future} that executes a query runner insert

-     * @see QueryRunner#insert(Connection, String, ResultSetHandler, Object...)

-     * @throws SQLException if a database access error occurs

-     * @since 1.6

-     */

-    public <T> Future<T> insert(final Connection conn, final String sql, final ResultSetHandler<T> rsh, final Object... params) throws SQLException {

-        return executorService.submit(() -> queryRunner.insert(conn, sql, rsh, params));

-    }

-

-    /**

-     * Executes {@link QueryRunner#insert(String, ResultSetHandler)} asynchronously.

-     *

-     * @param <T> Return type expected

-     * @param sql SQL insert statement to execute

-     * @param rsh {@link ResultSetHandler} for handling the results

-     * @return {@link Future} that executes a query runner insert

-     * @see QueryRunner#insert(String, ResultSetHandler)

-     * @throws SQLException if a database access error occurs

-     * @since 1.6

-     */

-    public <T> Future<T> insert(final String sql, final ResultSetHandler<T> rsh) throws SQLException {

-        return executorService.submit(() -> queryRunner.insert(sql, rsh));

-    }

-

-    /**

-     * Executes {@link QueryRunner#insert(String, ResultSetHandler, Object...)} asynchronously.

-     *

-     * @param <T> Return type expected

-     * @param sql SQL insert statement to execute

-     * @param rsh {@link ResultSetHandler} for handling the results

-     * @param params Parameter values for substitution in the SQL statement

-     * @return {@link Future} that executes a query runner insert

-     * @see QueryRunner#insert(String, ResultSetHandler, Object...)

-     * @throws SQLException if a database access error occurs

-     * @since 1.6

-     */

-    public <T> Future<T> insert(final String sql, final ResultSetHandler<T> rsh, final Object... params) throws SQLException {

-        return executorService.submit(() -> queryRunner.insert(sql, rsh, params));

-    }

-

-    /**

-     * {@link QueryRunner#insertBatch(Connection, String, ResultSetHandler, Object[][])} asynchronously.

-     *

-     * @param <T> Return type expected

-     * @param conn {@link Connection} to use to execute the SQL statement

-     * @param sql SQL insert statement to execute

-     * @param rsh {@link ResultSetHandler} for handling the results

-     * @param params An array of query replacement parameters.  Each row in

-     *        this array is one set of batch replacement values.

-     * @return {@link Future} that executes a query runner batch insert

-     * @see QueryRunner#insertBatch(Connection, String, ResultSetHandler, Object[][])

-     * @throws SQLException if a database access error occurs

-     * @since 1.6

-     */

-    public <T> Future<T> insertBatch(final Connection conn, final String sql, final ResultSetHandler<T> rsh, final Object[][] params) throws SQLException {

-        return executorService.submit(() -> queryRunner.insertBatch(conn, sql, rsh, params));

-    }

-

-    /**

-     * {@link QueryRunner#insertBatch(String, ResultSetHandler, Object[][])} asynchronously.

-     *

-     * @param <T> Return type expected

-     * @param sql SQL insert statement to execute

-     * @param rsh {@link ResultSetHandler} for handling the results

-     * @param params An array of query replacement parameters.  Each row in

-     *        this array is one set of batch replacement values.

-     * @return {@link Future} that executes a query runner batch insert

-     * @see QueryRunner#insertBatch(String, ResultSetHandler, Object[][])

-     * @throws SQLException if a database access error occurs

-     * @since 1.6

-     */

-    public <T> Future<T> insertBatch(final String sql, final ResultSetHandler<T> rsh, final Object[][] params) throws SQLException {

-        return executorService.submit(() -> queryRunner.insertBatch(sql, rsh, params));

-    }

-

-    /**

-     * Execute an SQL SELECT query without any replacement parameters.  The

-     * caller is responsible for closing the connection.

-     * @param <T> The type of object that the handler returns

-     * @param conn The connection to execute the query in.

-     * @param sql The query to execute.

-     * @param rsh The handler that converts the results into an object.

-     * @return A {@code Future} which returns the result of the query call.

-     * @throws SQLException if a database access error occurs

-     */

-    public <T> Future<T> query(final Connection conn, final String sql, final ResultSetHandler<T> rsh) throws SQLException {

-        return executorService.submit(() -> queryRunner.query(conn, sql, rsh));

-    }

-

-    /**

-     * Execute an SQL SELECT query with replacement parameters.  The

-     * caller is responsible for closing the connection.

-     * @param <T> The type of object that the handler returns

-     * @param conn The connection to execute the query in.

-     * @param sql The query to execute.

-     * @param rsh The handler that converts the results into an object.

-     * @param params The replacement parameters.

-     * @return A {@code Future} which returns the result of the query call.

-     * @throws SQLException if a database access error occurs

-     */

-    public <T> Future<T> query(final Connection conn, final String sql, final ResultSetHandler<T> rsh, final Object... params)

-            throws SQLException {

-        return executorService.submit(() -> queryRunner.query(conn, sql, rsh, params));

-    }

-

-    /**

-     * Executes the given SELECT SQL without any replacement parameters.

-     * The {@code Connection} is retrieved from the

-     * {@code DataSource} set in the constructor.

-     * @param <T> The type of object that the handler returns

-     * @param sql The SQL statement to execute.

-     * @param rsh The handler used to create the result object from

-     * the {@code ResultSet}.

-     *

-     * @return A {@code Future} which returns the result of the query call.

-     * @throws SQLException if a database access error occurs

-     */

-    public <T> Future<T> query(final String sql, final ResultSetHandler<T> rsh) throws SQLException {

-        return executorService.submit(() -> queryRunner.query(sql, rsh));

-    }

-

-    /**

-     * Executes the given SELECT SQL query and returns a result object.

-     * The {@code Connection} is retrieved from the

-     * {@code DataSource} set in the constructor.

-     * @param <T> The type of object that the handler returns

-     * @param sql The SQL statement to execute.

-     * @param rsh The handler used to create the result object from

-     * the {@code ResultSet}.

-     * @param params Initialize the PreparedStatement's IN parameters with

-     * this array.

-     * @return A {@code Future} which returns the result of the query call.

-     * @throws SQLException if a database access error occurs

-     */

-    public <T> Future<T> query(final String sql, final ResultSetHandler<T> rsh, final Object... params) throws SQLException {

-        return executorService.submit(() -> queryRunner.query(sql, rsh, params));

-    }

-

-    /**

-     * Execute an SQL INSERT, UPDATE, or DELETE query without replacement

-     * parameters.

-     *

-     * @param conn The connection to use to run the query.

-     * @param sql The SQL to execute.

-     * @return A {@code Future} which returns the number of rows updated.

-     * @throws SQLException if a database access error occurs

-     */

-    public Future<Integer> update(final Connection conn, final String sql) throws SQLException {

-        return executorService.submit(() -> Integer.valueOf(queryRunner.update(conn, sql)));

-    }

-

-    /**

-     * Execute an SQL INSERT, UPDATE, or DELETE query with a single replacement

-     * parameter.

-     *

-     * @param conn The connection to use to run the query.

-     * @param sql The SQL to execute.

-     * @param param The replacement parameter.

-     * @return A {@code Future} which returns the number of rows updated.

-     * @throws SQLException if a database access error occurs

-     */

-    public Future<Integer> update(final Connection conn, final String sql, final Object param) throws SQLException {

-        return executorService.submit(() -> Integer.valueOf(queryRunner.update(conn, sql, param)));

-    }

-

-    /**

-     * Execute an SQL INSERT, UPDATE, or DELETE query.

-     *

-     * @param conn The connection to use to run the query.

-     * @param sql The SQL to execute.

-     * @param params The query replacement parameters.

-     * @return A {@code Future} which returns the number of rows updated.

-     * @throws SQLException if a database access error occurs

-     */

-    public Future<Integer> update(final Connection conn, final String sql, final Object... params) throws SQLException {

-        return executorService.submit(() -> Integer.valueOf(queryRunner.update(conn, sql, params)));

-    }

-

-    /**

-     * Executes the given INSERT, UPDATE, or DELETE SQL statement without

-     * any replacement parameters. The {@code Connection} is retrieved

-     * from the {@code DataSource} set in the constructor.  This

-     * {@code Connection} must be in auto-commit mode or the update will

-     * not be saved.

-     *

-     * @param sql The SQL statement to execute.

-     * @throws SQLException if a database access error occurs

-     * @return A {@code Future} which returns the number of rows updated.

-     */

-    public Future<Integer> update(final String sql) throws SQLException {

-        return executorService.submit(() -> Integer.valueOf(queryRunner.update(sql)));

-    }

-

-    /**

-     * Executes the given INSERT, UPDATE, or DELETE SQL statement with

-     * a single replacement parameter.  The {@code Connection} is

-     * retrieved from the {@code DataSource} set in the constructor.

-     * This {@code Connection} must be in auto-commit mode or the

-     * update will not be saved.

-     *

-     * @param sql The SQL statement to execute.

-     * @param param The replacement parameter.

-     * @throws SQLException if a database access error occurs

-     * @return A {@code Future} which returns the number of rows updated.

-     */

-    public Future<Integer> update(final String sql, final Object param) throws SQLException {

-        return executorService.submit(() -> Integer.valueOf(queryRunner.update(sql, param)));

-    }

-

-    /**

-     * Executes the given INSERT, UPDATE, or DELETE SQL statement.  The

-     * {@code Connection} is retrieved from the {@code DataSource}

-     * set in the constructor.  This {@code Connection} must be in

-     * auto-commit mode or the update will not be saved.

-     *

-     * @param sql The SQL statement to execute.

-     * @param params Initializes the PreparedStatement's IN (i.e. '?')

-     * parameters.

-     * @throws SQLException if a database access error occurs

-     * @return A {@code Future} which returns the number of rows updated.

-     */

-    public Future<Integer> update(final String sql, final Object... params) throws SQLException {

-        return executorService.submit(() -> Integer.valueOf(queryRunner.update(sql, params)));

-    }

-

-}

+/*
+ * 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.dbutils;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+import javax.sql.DataSource;
+
+/**
+ * Executes SQL queries with pluggable strategies for handling
+ * {@code ResultSet}s.  This class is thread safe.
+ *
+ * @see ResultSetHandler
+ * @since 1.4
+ */
+public class AsyncQueryRunner extends AbstractQueryRunner {
+
+    /**
+     * @deprecated No longer used by this class. Will be removed in a future version.
+     * Class that encapsulates the continuation for batch calls.
+     */
+    @Deprecated
+    protected class BatchCallableStatement implements Callable<int[]> {
+        private final String sql;
+        private final Object[][] params;
+        private final Connection conn;
+        private final boolean closeConn;
+        private final PreparedStatement ps;
+
+        /**
+         * Creates a new BatchCallableStatement instance.
+         *
+         * @param sql The SQL statement to execute.
+         * @param params An array of query replacement parameters.  Each row in
+         *        this array is one set of batch replacement values.
+         * @param conn The connection to use for the batch call.
+         * @param closeConn True if the connection should be closed, false otherwise.
+         * @param ps The {@link PreparedStatement} to be executed.
+         */
+        public BatchCallableStatement(final String sql, final Object[][] params, final Connection conn, final boolean closeConn, final PreparedStatement ps) {
+            this.sql = sql;
+            this.params = params.clone();
+            this.conn = conn;
+            this.closeConn = closeConn;
+            this.ps = ps;
+        }
+
+        /**
+         * The actual call to executeBatch.
+         *
+         * @return an array of update counts containing one element for each command in the batch.
+         * @throws SQLException if a database access error occurs or one of the commands sent to the database fails.
+         * @see PreparedStatement#executeBatch()
+         */
+        @Override
+        public int[] call() throws SQLException {
+            int[] ret = null;
+
+            try {
+                ret = ps.executeBatch();
+            } catch (final SQLException e) {
+                rethrow(e, sql, (Object[])params);
+            } finally {
+                close(ps);
+                if (closeConn) {
+                    close(conn);
+                }
+            }
+
+            return ret;
+        }
+    }
+    /**
+     * Class that encapsulates the continuation for query calls.
+     * @param <T> The type of the result from the call to handle.
+     */
+    protected class QueryCallableStatement<T> implements Callable<T> {
+        private final String sql;
+        private final Object[] params;
+        private final Connection conn;
+        private final boolean closeConn;
+        private final PreparedStatement ps;
+        private final ResultSetHandler<T> rsh;
+
+        /**
+         * Creates a new {@code QueryCallableStatement} instance.
+         *
+         * @param conn The connection to use for the batch call.
+         * @param closeConn True if the connection should be closed, false otherwise.
+         * @param ps The {@link PreparedStatement} to be executed.
+         * @param rsh The handler that converts the results into an object.
+         * @param sql The SQL statement to execute.
+         * @param params An array of query replacement parameters.  Each row in
+         *        this array is one set of batch replacement values.
+         */
+        public QueryCallableStatement(final Connection conn, final boolean closeConn, final PreparedStatement ps,
+                final ResultSetHandler<T> rsh, final String sql, final Object... params) {
+            this.sql = sql;
+            this.params = params;
+            this.conn = conn;
+            this.closeConn = closeConn;
+            this.ps = ps;
+            this.rsh = rsh;
+        }
+
+        /**
+         * The actual call to {@code handle()} method.
+         *
+         * @return an array of update counts containing one element for each command in the batch.
+         * @throws SQLException if a database access error occurs.
+         * @see ResultSetHandler#handle(ResultSet)
+         */
+        @Override
+        public T call() throws SQLException {
+            ResultSet resultSet = null;
+            T ret = null;
+
+            try {
+                resultSet = wrap(ps.executeQuery());
+                ret = rsh.handle(resultSet);
+            } catch (final SQLException e) {
+                rethrow(e, sql, params);
+            } finally {
+                try {
+                    close(resultSet);
+                } finally {
+                    close(ps);
+                    if (closeConn) {
+                        close(conn);
+                    }
+                }
+            }
+
+            return ret;
+        }
+
+    }
+
+    /**
+     * Class that encapsulates the continuation for update calls.
+     *
+     * @deprecated No longer used by this class. Will be removed in a future version.
+     */
+    @Deprecated
+    protected class UpdateCallableStatement implements Callable<Integer> {
+        private final String sql;
+        private final Object[] params;
+        private final Connection conn;
+        private final boolean closeConn;
+        private final PreparedStatement ps;
+
+        /**
+         * Constructs a new instance.
+         *
+         * @param conn The connection to use for the batch call.
+         * @param closeConn True if the connection should be closed, false otherwise.
+         * @param ps The {@link PreparedStatement} to be executed.
+         * @param sql The SQL statement to execute.
+         * @param params An array of query replacement parameters.  Each row in
+         *        this array is one set of batch replacement values.
+         */
+        public UpdateCallableStatement(final Connection conn, final boolean closeConn, final PreparedStatement ps, final String sql, final Object... params) {
+            this.sql = sql;
+            this.params = params;
+            this.conn = conn;
+            this.closeConn = closeConn;
+            this.ps = ps;
+        }
+
+        /**
+         * The actual call to {@code executeUpdate()} method.
+         *
+         * @return either (1) the row count for SQL Data Manipulation Language (DML) statements or
+         *                (2) 0 for SQL statements that return nothing
+         * @throws SQLException if a database access error occurs.
+         * @see PreparedStatement#executeUpdate()
+         */
+        @Override
+        public Integer call() throws SQLException {
+            int rows = 0;
+
+            try {
+                rows = ps.executeUpdate();
+            } catch (final SQLException e) {
+                rethrow(e, sql, params);
+            } finally {
+                close(ps);
+                if (closeConn) {
+                    close(conn);
+                }
+            }
+
+            return Integer.valueOf(rows);
+        }
+
+    }
+
+    private final ExecutorService executorService;
+
+    private final QueryRunner queryRunner;
+
+    /**
+     * @deprecated Use {@link #AsyncQueryRunner(ExecutorService, QueryRunner)} instead.
+     * Constructor for AsyncQueryRunner that controls the use of {@code ParameterMetaData}.
+     *
+     * @param pmdKnownBroken Some drivers don't support {@link java.sql.ParameterMetaData#getParameterType(int) };
+     * if {@code pmdKnownBroken} is set to true, we won't even try it; if false, we'll try it,
+     * and if it breaks, we'll remember not to use it again.
+     * @param executorService the {@code ExecutorService} instance used to run JDBC invocations concurrently.
+     */
+    @Deprecated
+    public AsyncQueryRunner(final boolean pmdKnownBroken, final ExecutorService executorService) {
+        this(null, pmdKnownBroken, executorService);
+    }
+
+    /**
+     * @deprecated Use {@link #AsyncQueryRunner(ExecutorService, QueryRunner)} instead.
+     * Constructor for AsyncQueryRunner that take a {@code DataSource} and controls the use of {@code ParameterMetaData}.
+     * Methods that do not take a {@code Connection} parameter will retrieve connections from this
+     * {@code DataSource}.
+     *
+     * @param ds The {@code DataSource} to retrieve connections from.
+     * @param pmdKnownBroken Some drivers don't support {@link java.sql.ParameterMetaData#getParameterType(int) };
+     * if {@code pmdKnownBroken} is set to true, we won't even try it; if false, we'll try it,
+     * and if it breaks, we'll remember not to use it again.
+     * @param executorService the {@code ExecutorService} instance used to run JDBC invocations concurrently.
+     */
+    @Deprecated
+    public AsyncQueryRunner(final DataSource ds, final boolean pmdKnownBroken, final ExecutorService executorService) {
+        super(ds, pmdKnownBroken);
+        this.executorService = executorService;
+        this.queryRunner = new QueryRunner(ds, pmdKnownBroken);
+    }
+
+    /**
+     * @deprecated Use {@link #AsyncQueryRunner(ExecutorService, QueryRunner)} instead.
+     * Constructor for AsyncQueryRunner that takes a {@code DataSource}.
+     *
+     * Methods that do not take a {@code Connection} parameter will retrieve connections from this
+     * {@code DataSource}.
+     *
+     * @param ds The {@code DataSource} to retrieve connections from.
+     * @param executorService the {@code ExecutorService} instance used to run JDBC invocations concurrently.
+     */
+    @Deprecated
+    public AsyncQueryRunner(final DataSource ds, final ExecutorService executorService) {
+        this(ds, false, executorService);
+    }
+
+    /**
+     * Constructor for AsyncQueryRunner.
+     *
+     * @param executorService the {@code ExecutorService} instance used to run JDBC invocations concurrently.
+     */
+    public AsyncQueryRunner(final ExecutorService executorService) {
+        this(null, false, executorService);
+    }
+
+    /**
+     * Constructor for AsyncQueryRunner which uses a provided ExecutorService and underlying QueryRunner.
+     *
+     * @param executorService the {@code ExecutorService} instance used to run JDBC invocations concurrently.
+     * @param queryRunner the {@code QueryRunner} instance to use for the queries.
+     * @since 1.5
+     */
+    public AsyncQueryRunner(final ExecutorService executorService, final QueryRunner queryRunner) {
+        this.executorService = executorService;
+        this.queryRunner = queryRunner;
+    }
+
+    /**
+     * Execute a batch of SQL INSERT, UPDATE, or DELETE queries.
+     *
+     * @param conn The {@code Connection} to use to run the query.  The caller is
+     * responsible for closing this Connection.
+     * @param sql The SQL to execute.
+     * @param params An array of query replacement parameters.  Each row in
+     * this array is one set of batch replacement values.
+     * @return A {@code Future} which returns the number of rows updated per statement.
+     * @throws SQLException if a database access error occurs
+     */
+    public Future<int[]> batch(final Connection conn, final String sql, final Object[][] params) throws SQLException {
+        return executorService.submit(() -> queryRunner.batch(conn, sql, params));
+    }
+
+    /**
+     * Execute a batch of SQL INSERT, UPDATE, or DELETE queries.  The
+     * {@code Connection} is retrieved from the {@code DataSource}
+     * set in the constructor.  This {@code Connection} must be in
+     * auto-commit mode or the update will not be saved.
+     *
+     * @param sql The SQL to execute.
+     * @param params An array of query replacement parameters.  Each row in
+     * this array is one set of batch replacement values.
+     * @return A {@code Future} which returns the number of rows updated per statement.
+     * @throws SQLException if a database access error occurs
+     */
+    public Future<int[]> batch(final String sql, final Object[][] params) throws SQLException {
+        return executorService.submit(() -> queryRunner.batch(sql, params));
+    }
+
+    /**
+     * Executes {@link QueryRunner#insert(Connection, String, ResultSetHandler)} asynchronously.
+     *
+     * @param <T> Return type expected
+     * @param conn {@link Connection} to use to execute the SQL statement
+     * @param sql SQL insert statement to execute
+     * @param rsh {@link ResultSetHandler} for handling the results
+     * @return {@link Future} that executes a query runner insert
+     * @see QueryRunner#insert(Connection, String, ResultSetHandler)
+     * @throws SQLException if a database access error occurs
+     * @since 1.6
+     */
+    public <T> Future<T> insert(final Connection conn, final String sql, final ResultSetHandler<T> rsh) throws SQLException {
+        return executorService.submit(() -> queryRunner.insert(conn, sql, rsh));
+    }
+
+    /**
+     * Executes {@link QueryRunner#insert(Connection, String, ResultSetHandler, Object...)} asynchronously.
+     *
+     * @param <T> Return type expected
+     * @param conn {@link Connection} to use to execute the SQL statement
+     * @param sql SQL insert statement to execute
+     * @param rsh {@link ResultSetHandler} for handling the results
+     * @param params Parameter values for substitution in the SQL statement
+     * @return {@link Future} that executes a query runner insert
+     * @see QueryRunner#insert(Connection, String, ResultSetHandler, Object...)
+     * @throws SQLException if a database access error occurs
+     * @since 1.6
+     */
+    public <T> Future<T> insert(final Connection conn, final String sql, final ResultSetHandler<T> rsh, final Object... params) throws SQLException {
+        return executorService.submit(() -> queryRunner.insert(conn, sql, rsh, params));
+    }
+
+    /**
+     * Executes {@link QueryRunner#insert(String, ResultSetHandler)} asynchronously.
+     *
+     * @param <T> Return type expected
+     * @param sql SQL insert statement to execute
+     * @param rsh {@link ResultSetHandler} for handling the results
+     * @return {@link Future} that executes a query runner insert
+     * @see QueryRunner#insert(String, ResultSetHandler)
+     * @throws SQLException if a database access error occurs
+     * @since 1.6
+     */
+    public <T> Future<T> insert(final String sql, final ResultSetHandler<T> rsh) throws SQLException {
+        return executorService.submit(() -> queryRunner.insert(sql, rsh));
+    }
+
+    /**
+     * Executes {@link QueryRunner#insert(String, ResultSetHandler, Object...)} asynchronously.
+     *
+     * @param <T> Return type expected
+     * @param sql SQL insert statement to execute
+     * @param rsh {@link ResultSetHandler} for handling the results
+     * @param params Parameter values for substitution in the SQL statement
+     * @return {@link Future} that executes a query runner insert
+     * @see QueryRunner#insert(String, ResultSetHandler, Object...)
+     * @throws SQLException if a database access error occurs
+     * @since 1.6
+     */
+    public <T> Future<T> insert(final String sql, final ResultSetHandler<T> rsh, final Object... params) throws SQLException {
+        return executorService.submit(() -> queryRunner.insert(sql, rsh, params));
+    }
+
+    /**
+     * {@link QueryRunner#insertBatch(Connection, String, ResultSetHandler, Object[][])} asynchronously.
+     *
+     * @param <T> Return type expected
+     * @param conn {@link Connection} to use to execute the SQL statement
+     * @param sql SQL insert statement to execute
+     * @param rsh {@link ResultSetHandler} for handling the results
+     * @param params An array of query replacement parameters.  Each row in
+     *        this array is one set of batch replacement values.
+     * @return {@link Future} that executes a query runner batch insert
+     * @see QueryRunner#insertBatch(Connection, String, ResultSetHandler, Object[][])
+     * @throws SQLException if a database access error occurs
+     * @since 1.6
+     */
+    public <T> Future<T> insertBatch(final Connection conn, final String sql, final ResultSetHandler<T> rsh, final Object[][] params) throws SQLException {
+        return executorService.submit(() -> queryRunner.insertBatch(conn, sql, rsh, params));
+    }
+
+    /**
+     * {@link QueryRunner#insertBatch(String, ResultSetHandler, Object[][])} asynchronously.
+     *
+     * @param <T> Return type expected
+     * @param sql SQL insert statement to execute
+     * @param rsh {@link ResultSetHandler} for handling the results
+     * @param params An array of query replacement parameters.  Each row in
+     *        this array is one set of batch replacement values.
+     * @return {@link Future} that executes a query runner batch insert
+     * @see QueryRunner#insertBatch(String, ResultSetHandler, Object[][])
+     * @throws SQLException if a database access error occurs
+     * @since 1.6
+     */
+    public <T> Future<T> insertBatch(final String sql, final ResultSetHandler<T> rsh, final Object[][] params) throws SQLException {
+        return executorService.submit(() -> queryRunner.insertBatch(sql, rsh, params));
+    }
+
+    /**
+     * Execute an SQL SELECT query without any replacement parameters.  The
+     * caller is responsible for closing the connection.
+     * @param <T> The type of object that the handler returns
+     * @param conn The connection to execute the query in.
+     * @param sql The query to execute.
+     * @param rsh The handler that converts the results into an object.
+     * @return A {@code Future} which returns the result of the query call.
+     * @throws SQLException if a database access error occurs
+     */
+    public <T> Future<T> query(final Connection conn, final String sql, final ResultSetHandler<T> rsh) throws SQLException {
+        return executorService.submit(() -> queryRunner.query(conn, sql, rsh));
+    }
+
+    /**
+     * Execute an SQL SELECT query with replacement parameters.  The
+     * caller is responsible for closing the connection.
+     * @param <T> The type of object that the handler returns
+     * @param conn The connection to execute the query in.
+     * @param sql The query to execute.
+     * @param rsh The handler that converts the results into an object.
+     * @param params The replacement parameters.
+     * @return A {@code Future} which returns the result of the query call.
+     * @throws SQLException if a database access error occurs
+     */
+    public <T> Future<T> query(final Connection conn, final String sql, final ResultSetHandler<T> rsh, final Object... params)
+            throws SQLException {
+        return executorService.submit(() -> queryRunner.query(conn, sql, rsh, params));
+    }
+
+    /**
+     * Executes the given SELECT SQL without any replacement parameters.
+     * The {@code Connection} is retrieved from the
+     * {@code DataSource} set in the constructor.
+     * @param <T> The type of object that the handler returns
+     * @param sql The SQL statement to execute.
+     * @param rsh The handler used to create the result object from
+     * the {@code ResultSet}.
+     *
+     * @return A {@code Future} which returns the result of the query call.
+     * @throws SQLException if a database access error occurs
+     */
+    public <T> Future<T> query(final String sql, final ResultSetHandler<T> rsh) throws SQLException {
+        return executorService.submit(() -> queryRunner.query(sql, rsh));
+    }
+
+    /**
+     * Executes the given SELECT SQL query and returns a result object.
+     * The {@code Connection} is retrieved from the
+     * {@code DataSource} set in the constructor.
+     * @param <T> The type of object that the handler returns
+     * @param sql The SQL statement to execute.
+     * @param rsh The handler used to create the result object from
+     * the {@code ResultSet}.
+     * @param params Initialize the PreparedStatement's IN parameters with
+     * this array.
+     * @return A {@code Future} which returns the result of the query call.
+     * @throws SQLException if a database access error occurs
+     */
+    public <T> Future<T> query(final String sql, final ResultSetHandler<T> rsh, final Object... params) throws SQLException {
+        return executorService.submit(() -> queryRunner.query(sql, rsh, params));
+    }
+
+    /**
+     * Execute an SQL INSERT, UPDATE, or DELETE query without replacement
+     * parameters.
+     *
+     * @param conn The connection to use to run the query.
+     * @param sql The SQL to execute.
+     * @return A {@code Future} which returns the number of rows updated.
+     * @throws SQLException if a database access error occurs
+     */
+    public Future<Integer> update(final Connection conn, final String sql) throws SQLException {
+        return executorService.submit(() -> Integer.valueOf(queryRunner.update(conn, sql)));
+    }
+
+    /**
+     * Execute an SQL INSERT, UPDATE, or DELETE query with a single replacement
+     * parameter.
+     *
+     * @param conn The connection to use to run the query.
+     * @param sql The SQL to execute.
+     * @param param The replacement parameter.
+     * @return A {@code Future} which returns the number of rows updated.
+     * @throws SQLException if a database access error occurs
+     */
+    public Future<Integer> update(final Connection conn, final String sql, final Object param) throws SQLException {
+        return executorService.submit(() -> Integer.valueOf(queryRunner.update(conn, sql, param)));
+    }
+
+    /**
+     * Execute an SQL INSERT, UPDATE, or DELETE query.
+     *
+     * @param conn The connection to use to run the query.
+     * @param sql The SQL to execute.
+     * @param params The query replacement parameters.
+     * @return A {@code Future} which returns the number of rows updated.
+     * @throws SQLException if a database access error occurs
+     */
+    public Future<Integer> update(final Connection conn, final String sql, final Object... params) throws SQLException {
+        return executorService.submit(() -> Integer.valueOf(queryRunner.update(conn, sql, params)));
+    }
+
+    /**
+     * Executes the given INSERT, UPDATE, or DELETE SQL statement without
+     * any replacement parameters. The {@code Connection} is retrieved
+     * from the {@code DataSource} set in the constructor.  This
+     * {@code Connection} must be in auto-commit mode or the update will
+     * not be saved.
+     *
+     * @param sql The SQL statement to execute.
+     * @throws SQLException if a database access error occurs
+     * @return A {@code Future} which returns the number of rows updated.
+     */
+    public Future<Integer> update(final String sql) throws SQLException {
+        return executorService.submit(() -> Integer.valueOf(queryRunner.update(sql)));
+    }
+
+    /**
+     * Executes the given INSERT, UPDATE, or DELETE SQL statement with
+     * a single replacement parameter.  The {@code Connection} is
+     * retrieved from the {@code DataSource} set in the constructor.
+     * This {@code Connection} must be in auto-commit mode or the
+     * update will not be saved.
+     *
+     * @param sql The SQL statement to execute.
+     * @param param The replacement parameter.
+     * @throws SQLException if a database access error occurs
+     * @return A {@code Future} which returns the number of rows updated.
+     */
+    public Future<Integer> update(final String sql, final Object param) throws SQLException {
+        return executorService.submit(() -> Integer.valueOf(queryRunner.update(sql, param)));
+    }
+
+    /**
+     * Executes the given INSERT, UPDATE, or DELETE SQL statement.  The
+     * {@code Connection} is retrieved from the {@code DataSource}
+     * set in the constructor.  This {@code Connection} must be in
+     * auto-commit mode or the update will not be saved.
+     *
+     * @param sql The SQL statement to execute.
+     * @param params Initializes the PreparedStatement's IN (i.e. '?')
+     * parameters.
+     * @throws SQLException if a database access error occurs
+     * @return A {@code Future} which returns the number of rows updated.
+     */
+    public Future<Integer> update(final String sql, final Object... params) throws SQLException {
+        return executorService.submit(() -> Integer.valueOf(queryRunner.update(sql, params)));
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/dbutils/BasicRowProcessor.java b/src/main/java/org/apache/commons/dbutils/BasicRowProcessor.java
index 3d90f7b..4ed6e3c 100644
--- a/src/main/java/org/apache/commons/dbutils/BasicRowProcessor.java
+++ b/src/main/java/org/apache/commons/dbutils/BasicRowProcessor.java
@@ -1,274 +1,274 @@
-/*

- * 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.dbutils;

-

-import java.sql.ResultSet;

-import java.sql.ResultSetMetaData;

-import java.sql.SQLException;

-import java.util.HashMap;

-import java.util.LinkedHashMap;

-import java.util.List;

-import java.util.Locale;

-import java.util.Map;

-

-/**

- * Basic implementation of the {@code RowProcessor} interface.

- *

- * <p>

- * This class is thread-safe.

- * </p>

- *

- * @see RowProcessor

- */

-public class BasicRowProcessor implements RowProcessor {

-

-    /**

-     * A Map that converts all keys to lowercase Strings for case insensitive

-     * lookups.  This is needed for the toMap() implementation because

-     * databases don't consistently handle the casing of column names.

-     *

-     * <p>The keys are stored as they are given [BUG #DBUTILS-34], so we maintain

-     * an internal mapping from lowercase keys to the real keys in order to

-     * achieve the case insensitive lookup.

-     *

-     * <p>Note: This implementation does not allow {@code null}

-     * for key, whereas {@link LinkedHashMap} does, because of the code:

-     * <pre>

-     * key.toString().toLowerCase()

-     * </pre>

-     */

-    private static final class CaseInsensitiveHashMap extends LinkedHashMap<String, Object> {

-

-        /**

-         * Required for serialization support.

-         *

-         * @see java.io.Serializable

-         */

-        private static final long serialVersionUID = -2848100435296897392L;

-

-        /**

-         * The internal mapping from lowercase keys to the real keys.

-         *

-         * <p>

-         * Any query operation using the key

-         * ({@link #get(Object)}, {@link #containsKey(Object)})

-         * is done in three steps:

-         * <ul>

-         * <li>convert the parameter key to lower case</li>

-         * <li>get the actual key that corresponds to the lower case key</li>

-         * <li>query the map with the actual key</li>

-         * </ul>

-         * </p>

-         */

-        private final Map<String, String> lowerCaseMap = new HashMap<>();

-

-        private CaseInsensitiveHashMap(final int initialCapacity) {

-            super(initialCapacity);

-        }

-

-        /** {@inheritDoc} */

-        @Override

-        public boolean containsKey(final Object key) {

-            final Object realKey = lowerCaseMap.get(key.toString().toLowerCase(Locale.ROOT));

-            return super.containsKey(realKey);

-            // Possible optimization here:

-            // Since the lowerCaseMap contains a mapping for all the keys,

-            // we could just do this:

-            // return lowerCaseMap.containsKey(key.toString().toLowerCase());

-        }

-

-        /** {@inheritDoc} */

-        @Override

-        public Object get(final Object key) {

-            final Object realKey = lowerCaseMap.get(key.toString().toLowerCase(Locale.ROOT));

-            return super.get(realKey);

-        }

-

-        /** {@inheritDoc} */

-        @Override

-        public Object put(final String key, final Object value) {

-            /*

-             * In order to keep the map and lowerCaseMap synchronized,

-             * we have to remove the old mapping before putting the

-             * new one. Indeed, oldKey and key are not necessarily equals.

-             * (That's why we call super.remove(oldKey) and not just

-             * super.put(key, value))

-             */

-            final Object oldKey = lowerCaseMap.put(key.toLowerCase(Locale.ROOT), key);

-            final Object oldValue = super.remove(oldKey);

-            super.put(key, value);

-            return oldValue;

-        }

-

-        /** {@inheritDoc} */

-        @Override

-        public void putAll(final Map<? extends String, ?> m) {

-            m.forEach(this::put);

-        }

-

-        /** {@inheritDoc} */

-        @Override

-        public Object remove(final Object key) {

-            final Object realKey = lowerCaseMap.remove(key.toString().toLowerCase(Locale.ROOT));

-            return super.remove(realKey);

-        }

-    }

-

-    /**

-     * The default BeanProcessor instance to use if not supplied in the

-     * constructor.

-     */

-    private static final BeanProcessor DEFAULT_CONVERTER = new BeanProcessor();

-

-    /**

-     * The Singleton instance of this class.

-     */

-    private static final BasicRowProcessor INSTANCE = new BasicRowProcessor();

-

-    protected static Map<String, Object> createCaseInsensitiveHashMap(final int cols) {

-        return new CaseInsensitiveHashMap(cols);

-    }

-

-    /**

-     * Returns the Singleton instance of this class.

-     *

-     * @return The single instance of this class.

-     * @deprecated Create instances with the constructors instead.  This will

-     * be removed in 2.0.

-     */

-    @Deprecated

-    public static BasicRowProcessor instance() {

-        return INSTANCE;

-    }

-

-    /**

-     * Use this to process beans.

-     */

-    private final BeanProcessor convert;

-

-    /**

-     * BasicRowProcessor constructor.  Bean processing defaults to a

-     * BeanProcessor instance.

-     */

-    public BasicRowProcessor() {

-        this(DEFAULT_CONVERTER);

-    }

-

-    /**

-     * BasicRowProcessor constructor.

-     * @param convert The BeanProcessor to use when converting columns to

-     * bean properties.

-     * @since 1.1

-     */

-    public BasicRowProcessor(final BeanProcessor convert) {

-        this.convert = convert;

-    }

-

-    /**

-     * Convert a {@code ResultSet} row into an {@code Object[]}.

-     * This implementation copies column values into the array in the same

-     * order they're returned from the {@code ResultSet}.  Array elements

-     * will be set to {@code null} if the column was SQL NULL.

-     *

-     * @see org.apache.commons.dbutils.RowProcessor#toArray(java.sql.ResultSet)

-     * @param resultSet ResultSet that supplies the array data

-     * @throws SQLException if a database access error occurs

-     * @return the newly created array

-     */

-    @Override

-    public Object[] toArray(final ResultSet resultSet) throws SQLException {

-        final ResultSetMetaData meta = resultSet.getMetaData();

-        final int cols = meta.getColumnCount();

-        final Object[] result = new Object[cols];

-

-        for (int i = 0; i < cols; i++) {

-            result[i] = resultSet.getObject(i + 1);

-        }

-

-        return result;

-    }

-

-    /**

-     * Convert a {@code ResultSet} row into a JavaBean.  This

-     * implementation delegates to a BeanProcessor instance.

-     * @see org.apache.commons.dbutils.RowProcessor#toBean(java.sql.ResultSet, Class)

-     * @see org.apache.commons.dbutils.BeanProcessor#toBean(java.sql.ResultSet, Class)

-     * @param <T> The type of bean to create

-     * @param resultSet ResultSet that supplies the bean data

-     * @param type Class from which to create the bean instance

-     * @throws SQLException if a database access error occurs

-     * @return the newly created bean

-     */

-    @Override

-    public <T> T toBean(final ResultSet resultSet, final Class<? extends T> type) throws SQLException {

-        return this.convert.toBean(resultSet, type);

-    }

-

-    /**

-     * Convert a {@code ResultSet} into a {@code List} of JavaBeans.

-     * This implementation delegates to a BeanProcessor instance.

-     * @see org.apache.commons.dbutils.RowProcessor#toBeanList(java.sql.ResultSet, Class)

-     * @see org.apache.commons.dbutils.BeanProcessor#toBeanList(java.sql.ResultSet, Class)

-     * @param <T> The type of bean to create

-     * @param resultSet ResultSet that supplies the bean data

-     * @param type Class from which to create the bean instance

-     * @throws SQLException if a database access error occurs

-     * @return A {@code List} of beans with the given type in the order

-     * they were returned by the {@code ResultSet}.

-     */

-    @Override

-    public <T> List<T> toBeanList(final ResultSet resultSet, final Class<? extends T> type) throws SQLException {

-        return this.convert.toBeanList(resultSet, type);

-    }

-

-    /**

-     * Convert a {@code ResultSet} row into a {@code Map}.

-     *

-     * <p>

-     * This implementation returns a {@code Map} with case insensitive column names as keys. Calls to

-     * {@code map.get("COL")} and {@code map.get("col")} return the same value. Furthermore this implementation

-     * will return an ordered map, that preserves the ordering of the columns in the ResultSet, so that iterating over

-     * the entry set of the returned map will return the first column of the ResultSet, then the second and so forth.

-     * </p>

-     *

-     * @param resultSet ResultSet that supplies the map data

-     * @return the newly created Map

-     * @throws SQLException if a database access error occurs

-     * @see org.apache.commons.dbutils.RowProcessor#toMap(java.sql.ResultSet)

-     */

-    @Override

-    public Map<String, Object> toMap(final ResultSet resultSet) throws SQLException {

-        final ResultSetMetaData rsmd = resultSet.getMetaData();

-        final int cols = rsmd.getColumnCount();

-        final Map<String, Object> result = createCaseInsensitiveHashMap(cols);

-

-        for (int i = 1; i <= cols; i++) {

-            String propKey = rsmd.getColumnLabel(i);

-            if (null == propKey || 0 == propKey.length()) {

-              propKey = rsmd.getColumnName(i);

-            }

-            if (null == propKey || 0 == propKey.length()) {

-              // The column index can't be null

-              propKey = Integer.toString(i);

-            }

-            result.put(propKey, resultSet.getObject(i));

-        }

-

-        return result;

-    }

-

-}

+/*
+ * 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.dbutils;
+
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Basic implementation of the {@code RowProcessor} interface.
+ *
+ * <p>
+ * This class is thread-safe.
+ * </p>
+ *
+ * @see RowProcessor
+ */
+public class BasicRowProcessor implements RowProcessor {
+
+    /**
+     * A Map that converts all keys to lowercase Strings for case insensitive
+     * lookups.  This is needed for the toMap() implementation because
+     * databases don't consistently handle the casing of column names.
+     *
+     * <p>The keys are stored as they are given [BUG #DBUTILS-34], so we maintain
+     * an internal mapping from lowercase keys to the real keys in order to
+     * achieve the case insensitive lookup.
+     *
+     * <p>Note: This implementation does not allow {@code null}
+     * for key, whereas {@link LinkedHashMap} does, because of the code:
+     * <pre>
+     * key.toString().toLowerCase()
+     * </pre>
+     */
+    private static final class CaseInsensitiveHashMap extends LinkedHashMap<String, Object> {
+
+        /**
+         * Required for serialization support.
+         *
+         * @see java.io.Serializable
+         */
+        private static final long serialVersionUID = -2848100435296897392L;
+
+        /**
+         * The internal mapping from lowercase keys to the real keys.
+         *
+         * <p>
+         * Any query operation using the key
+         * ({@link #get(Object)}, {@link #containsKey(Object)})
+         * is done in three steps:
+         * <ul>
+         * <li>convert the parameter key to lower case</li>
+         * <li>get the actual key that corresponds to the lower case key</li>
+         * <li>query the map with the actual key</li>
+         * </ul>
+         * </p>
+         */
+        private final Map<String, String> lowerCaseMap = new HashMap<>();
+
+        private CaseInsensitiveHashMap(final int initialCapacity) {
+            super(initialCapacity);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public boolean containsKey(final Object key) {
+            final Object realKey = lowerCaseMap.get(key.toString().toLowerCase(Locale.ROOT));
+            return super.containsKey(realKey);
+            // Possible optimization here:
+            // Since the lowerCaseMap contains a mapping for all the keys,
+            // we could just do this:
+            // return lowerCaseMap.containsKey(key.toString().toLowerCase());
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Object get(final Object key) {
+            final Object realKey = lowerCaseMap.get(key.toString().toLowerCase(Locale.ROOT));
+            return super.get(realKey);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Object put(final String key, final Object value) {
+            /*
+             * In order to keep the map and lowerCaseMap synchronized,
+             * we have to remove the old mapping before putting the
+             * new one. Indeed, oldKey and key are not necessarily equals.
+             * (That's why we call super.remove(oldKey) and not just
+             * super.put(key, value))
+             */
+            final Object oldKey = lowerCaseMap.put(key.toLowerCase(Locale.ROOT), key);
+            final Object oldValue = super.remove(oldKey);
+            super.put(key, value);
+            return oldValue;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void putAll(final Map<? extends String, ?> m) {
+            m.forEach(this::put);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Object remove(final Object key) {
+            final Object realKey = lowerCaseMap.remove(key.toString().toLowerCase(Locale.ROOT));
+            return super.remove(realKey);
+        }
+    }
+
+    /**
+     * The default BeanProcessor instance to use if not supplied in the
+     * constructor.
+     */
+    private static final BeanProcessor DEFAULT_CONVERTER = new BeanProcessor();
+
+    /**
+     * The Singleton instance of this class.
+     */
+    private static final BasicRowProcessor INSTANCE = new BasicRowProcessor();
+
+    protected static Map<String, Object> createCaseInsensitiveHashMap(final int cols) {
+        return new CaseInsensitiveHashMap(cols);
+    }
+
+    /**
+     * Returns the Singleton instance of this class.
+     *
+     * @return The single instance of this class.
+     * @deprecated Create instances with the constructors instead.  This will
+     * be removed in 2.0.
+     */
+    @Deprecated
+    public static BasicRowProcessor instance() {
+        return INSTANCE;
+    }
+
+    /**
+     * Use this to process beans.
+     */
+    private final BeanProcessor convert;
+
+    /**
+     * BasicRowProcessor constructor.  Bean processing defaults to a
+     * BeanProcessor instance.
+     */
+    public BasicRowProcessor() {
+        this(DEFAULT_CONVERTER);
+    }
+
+    /**
+     * BasicRowProcessor constructor.
+     * @param convert The BeanProcessor to use when converting columns to
+     * bean properties.
+     * @since 1.1
+     */
+    public BasicRowProcessor(final BeanProcessor convert) {
+        this.convert = convert;
+    }
+
+    /**
+     * Convert a {@code ResultSet} row into an {@code Object[]}.
+     * This implementation copies column values into the array in the same
+     * order they're returned from the {@code ResultSet}.  Array elements
+     * will be set to {@code null} if the column was SQL NULL.
+     *
+     * @see org.apache.commons.dbutils.RowProcessor#toArray(java.sql.ResultSet)
+     * @param resultSet ResultSet that supplies the array data
+     * @throws SQLException if a database access error occurs
+     * @return the newly created array
+     */
+    @Override
+    public Object[] toArray(final ResultSet resultSet) throws SQLException {
+        final ResultSetMetaData meta = resultSet.getMetaData();
+        final int cols = meta.getColumnCount();
+        final Object[] result = new Object[cols];
+
+        for (int i = 0; i < cols; i++) {
+            result[i] = resultSet.getObject(i + 1);
+        }
+
+        return result;
+    }
+
+    /**
+     * Convert a {@code ResultSet} row into a JavaBean.  This
+     * implementation delegates to a BeanProcessor instance.
+     * @see org.apache.commons.dbutils.RowProcessor#toBean(java.sql.ResultSet, Class)
+     * @see org.apache.commons.dbutils.BeanProcessor#toBean(java.sql.ResultSet, Class)
+     * @param <T> The type of bean to create
+     * @param resultSet ResultSet that supplies the bean data
+     * @param type Class from which to create the bean instance
+     * @throws SQLException if a database access error occurs
+     * @return the newly created bean
+     */
+    @Override
+    public <T> T toBean(final ResultSet resultSet, final Class<? extends T> type) throws SQLException {
+        return this.convert.toBean(resultSet, type);
+    }
+
+    /**
+     * Convert a {@code ResultSet} into a {@code List} of JavaBeans.
+     * This implementation delegates to a BeanProcessor instance.
+     * @see org.apache.commons.dbutils.RowProcessor#toBeanList(java.sql.ResultSet, Class)
+     * @see org.apache.commons.dbutils.BeanProcessor#toBeanList(java.sql.ResultSet, Class)
+     * @param <T> The type of bean to create
+     * @param resultSet ResultSet that supplies the bean data
+     * @param type Class from which to create the bean instance
+     * @throws SQLException if a database access error occurs
+     * @return A {@code List} of beans with the given type in the order
+     * they were returned by the {@code ResultSet}.
+     */
+    @Override
+    public <T> List<T> toBeanList(final ResultSet resultSet, final Class<? extends T> type) throws SQLException {
+        return this.convert.toBeanList(resultSet, type);
+    }
+
+    /**
+     * Convert a {@code ResultSet} row into a {@code Map}.
+     *
+     * <p>
+     * This implementation returns a {@code Map} with case insensitive column names as keys. Calls to
+     * {@code map.get("COL")} and {@code map.get("col")} return the same value. Furthermore this implementation
+     * will return an ordered map, that preserves the ordering of the columns in the ResultSet, so that iterating over
+     * the entry set of the returned map will return the first column of the ResultSet, then the second and so forth.
+     * </p>
+     *
+     * @param resultSet ResultSet that supplies the map data
+     * @return the newly created Map
+     * @throws SQLException if a database access error occurs
+     * @see org.apache.commons.dbutils.RowProcessor#toMap(java.sql.ResultSet)
+     */
+    @Override
+    public Map<String, Object> toMap(final ResultSet resultSet) throws SQLException {
+        final ResultSetMetaData rsmd = resultSet.getMetaData();
+        final int cols = rsmd.getColumnCount();
+        final Map<String, Object> result = createCaseInsensitiveHashMap(cols);
+
+        for (int i = 1; i <= cols; i++) {
+            String propKey = rsmd.getColumnLabel(i);
+            if (null == propKey || 0 == propKey.length()) {
+              propKey = rsmd.getColumnName(i);
+            }
+            if (null == propKey || 0 == propKey.length()) {
+              // The column index can't be null
+              propKey = Integer.toString(i);
+            }
+            result.put(propKey, resultSet.getObject(i));
+        }
+
+        return result;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/dbutils/BeanProcessor.java b/src/main/java/org/apache/commons/dbutils/BeanProcessor.java
index abd5c89..b65cb56 100644
--- a/src/main/java/org/apache/commons/dbutils/BeanProcessor.java
+++ b/src/main/java/org/apache/commons/dbutils/BeanProcessor.java
@@ -1,519 +1,519 @@
-/*

- * 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.dbutils;

-

-import java.beans.BeanInfo;

-import java.beans.IntrospectionException;

-import java.beans.Introspector;

-import java.beans.PropertyDescriptor;

-import java.lang.reflect.Field;

-import java.lang.reflect.InvocationTargetException;

-import java.lang.reflect.Method;

-import java.sql.ResultSet;

-import java.sql.ResultSetMetaData;

-import java.sql.SQLException;

-import java.util.ArrayList;

-import java.util.Arrays;

-import java.util.HashMap;

-import java.util.List;

-import java.util.Map;

-import java.util.ServiceLoader;

-

-/**

- * <p>

- * {@code BeanProcessor} matches column names to bean property names

- * and converts {@code ResultSet} columns into objects for those bean

- * properties.  Subclasses should override the methods in the processing chain

- * to customize behavior.

- * </p>

- *

- * <p>

- * This class is thread-safe.

- * </p>

- *

- * @see BasicRowProcessor

- * @since 1.1

- */

-public class BeanProcessor {

-

-    /**

-     * Special array value used by {@code mapColumnsToProperties} that

-     * indicates there is no bean property that matches a column from a

-     * {@code ResultSet}.

-     */

-    protected static final int PROPERTY_NOT_FOUND = -1;

-

-    /**

-     * Set a bean's primitive properties to these defaults when SQL NULL

-     * is returned.  These are the same as the defaults that ResultSet get*

-     * methods return in the event of a NULL column.

-     */

-    private static final Map<Class<?>, Object> PRIMITIVE_DEFAULTS = new HashMap<>();

-

-    private static final List<ColumnHandler<?>> COLUMN_HANDLERS = new ArrayList<>();

-

-    private static final List<PropertyHandler> PROPERTY_HANDLERS = new ArrayList<>();

-

-    static {

-        PRIMITIVE_DEFAULTS.put(Integer.TYPE, Integer.valueOf(0));

-        PRIMITIVE_DEFAULTS.put(Short.TYPE, Short.valueOf((short) 0));

-        PRIMITIVE_DEFAULTS.put(Byte.TYPE, Byte.valueOf((byte) 0));

-        PRIMITIVE_DEFAULTS.put(Float.TYPE, Float.valueOf(0f));

-        PRIMITIVE_DEFAULTS.put(Double.TYPE, Double.valueOf(0d));

-        PRIMITIVE_DEFAULTS.put(Long.TYPE, Long.valueOf(0L));

-        PRIMITIVE_DEFAULTS.put(Boolean.TYPE, Boolean.FALSE);

-        PRIMITIVE_DEFAULTS.put(Character.TYPE, Character.valueOf((char) 0));

-

-        // Use a ServiceLoader to find implementations

-        ServiceLoader.load(ColumnHandler.class).forEach(COLUMN_HANDLERS::add);

-

-        // Use a ServiceLoader to find implementations

-        ServiceLoader.load(PropertyHandler.class).forEach(PROPERTY_HANDLERS::add);

-    }

-

-    /**

-     * ResultSet column to bean property name overrides.

-     */

-    private final Map<String, String> columnToPropertyOverrides;

-

-    /**

-     * Constructor for BeanProcessor.

-     */

-    public BeanProcessor() {

-        this(new HashMap<>());

-    }

-

-    /**

-     * Constructor for BeanProcessor configured with column to property name overrides.

-     *

-     * @param columnToPropertyOverrides ResultSet column to bean property name overrides

-     * @since 1.5

-     */

-    public BeanProcessor(final Map<String, String> columnToPropertyOverrides) {

-        if (columnToPropertyOverrides == null) {

-            throw new IllegalArgumentException("columnToPropertyOverrides map cannot be null");

-        }

-        this.columnToPropertyOverrides = columnToPropertyOverrides;

-    }

-

-    /**

-     * Calls the setter method on the target object for the given property.

-     * If no setter method exists for the property, this method does nothing.

-     * @param target The object to set the property on.

-     * @param prop The property to set.

-     * @param value The value to pass into the setter.

-     * @throws SQLException if an error occurs setting the property.

-     */

-    private void callSetter(final Object target, final PropertyDescriptor prop, Object value)

-            throws SQLException {

-

-        final Method setter = getWriteMethod(target, prop, value);

-

-        if (setter == null || setter.getParameterTypes().length != 1) {

-            return;

-        }

-

-        try {

-            final Class<?> firstParam = setter.getParameterTypes()[0];

-            for (final PropertyHandler handler : PROPERTY_HANDLERS) {

-                if (handler.match(firstParam, value)) {

-                    value = handler.apply(firstParam, value);

-                    break;

-                }

-            }

-

-            // Don't call setter if the value object isn't the right type

-            if (!isCompatibleType(value, firstParam)) {

-                throw new SQLException(

-                        "Cannot set " + prop.getName() + ": incompatible types, cannot convert " + value.getClass().getName() + " to " + firstParam.getName());

-                // value cannot be null here because isCompatibleType allows null

-            }

-            setter.invoke(target, value);

-

-        } catch (final IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {

-            throw new SQLException("Cannot set " + prop.getName() + ": " + e.getMessage());

-        }

-    }

-

-    /**

-     * Creates a new object and initializes its fields from the ResultSet.

-     * @param <T> The type of bean to create

-     * @param resultSet The result set.

-     * @param type The bean type (the return type of the object).

-     * @param props The property descriptors.

-     * @param columnToProperty The column indices in the result set.

-     * @return An initialized object.

-     * @throws SQLException if a database error occurs.

-     */

-    private <T> T createBean(final ResultSet resultSet, final Class<T> type, final PropertyDescriptor[] props, final int[] columnToProperty)

-            throws SQLException {

-        return populateBean(resultSet, this.newInstance(type), props, columnToProperty);

-    }

-

-    /** Does nothing. */

-    @Override

-    protected final void finalize() {

-        // SpotBugs CT_CONSTRUCTOR_THROW

-    }

-

-    /**

-     * Gets the write method to use when setting {@code value} to the {@code target}.

-     *

-     * @param target Object where the write method will be called.

-     * @param prop   BeanUtils information.

-     * @param value  The value that will be passed to the write method.

-     * @return The {@link java.lang.reflect.Method} to call on {@code target} to write {@code value} or {@code null} if

-     *         there is no suitable write method.

-     */

-    protected Method getWriteMethod(final Object target, final PropertyDescriptor prop, final Object value) {

-        return prop.getWriteMethod();

-    }

-

-    /**

-     * ResultSet.getObject() returns an Integer object for an INT column.  The

-     * setter method for the property might take an Integer or a primitive int.

-     * This method returns true if the value can be successfully passed into

-     * the setter method.  Remember, Method.invoke() handles the unwrapping

-     * of Integer into an int.

-     *

-     * @param value The value to be passed into the setter method.

-     * @param type The setter's parameter type (non-null)

-     * @return boolean True if the value is compatible (null => true)

-     */

-    private boolean isCompatibleType(final Object value, final Class<?> type) {

-        // Do object check first, then primitives

-        return value == null || type.isInstance(value) || matchesPrimitive(type, value.getClass());

-    }

-

-    /**

-     * The positions in the returned array represent column numbers.  The

-     * values stored at each position represent the index in the

-     * {@code PropertyDescriptor[]} for the bean property that matches

-     * the column name.  If no bean property was found for a column, the

-     * position is set to {@code PROPERTY_NOT_FOUND}.

-     *

-     * @param rsmd The {@code ResultSetMetaData} containing column

-     * information.

-     *

-     * @param props The bean property descriptors.

-     * @throws SQLException if a database access error occurs

-     * @return An int[] with column index to property index mappings.  The 0th

-     * element is meaningless because JDBC column indexing starts at 1.

-     */

-    protected int[] mapColumnsToProperties(final ResultSetMetaData rsmd,

-            final PropertyDescriptor[] props) throws SQLException {

-

-        final int cols = rsmd.getColumnCount();

-        final int[] columnToProperty = new int[cols + 1];

-        Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);

-

-        for (int col = 1; col <= cols; col++) {

-            String columnName = rsmd.getColumnLabel(col);

-            if (null == columnName || 0 == columnName.length()) {

-              columnName = rsmd.getColumnName(col);

-            }

-            String propertyName = columnToPropertyOverrides.get(columnName);

-            if (propertyName == null) {

-                propertyName = columnName;

-            }

-            if (propertyName == null) {

-                propertyName = Integer.toString(col);

-            }

-

-            for (int i = 0; i < props.length; i++) {

-                final PropertyDescriptor prop = props[i];

-                final Method reader = prop.getReadMethod();

-

-                // Check for @Column annotations as explicit marks

-                final Column column;

-                if (reader != null) {

-                    column = reader.getAnnotation(Column.class);

-                } else {

-                    column = null;

-                }

-

-                final String propertyColumnName;

-                if (column != null) {

-                    propertyColumnName = column.name();

-                } else {

-                    propertyColumnName = prop.getName();

-                }

-                if (propertyName.equalsIgnoreCase(propertyColumnName)) {

-                    columnToProperty[col] = i;

-                    break;

-                }

-            }

-        }

-

-        return columnToProperty;

-    }

-

-    /**

-     * Check whether a value is of the same primitive type as {@code targetType}.

-     *

-     * @param targetType The primitive type to target.

-     * @param valueType The value to match to the primitive type.

-     * @return Whether {@code valueType} can be coerced (e.g. autoboxed) into {@code targetType}.

-     */

-    private boolean matchesPrimitive(final Class<?> targetType, final Class<?> valueType) {

-        if (!targetType.isPrimitive()) {

-            return false;

-        }

-

-        try {

-            // see if there is a "TYPE" field.  This is present for primitive wrappers.

-            final Field typeField = valueType.getField("TYPE");

-            final Object primitiveValueType = typeField.get(valueType);

-

-            if (targetType == primitiveValueType) {

-                return true;

-            }

-        } catch (final NoSuchFieldException | IllegalAccessException ignored) {

-            // an inaccessible TYPE field is a good sign that we're not working with a primitive wrapper.

-            // nothing to do.  we can't match for compatibility

-        }

-        return false;

-    }

-

-    /**

-     * Factory method that returns a new instance of the given Class.  This

-     * is called at the start of the bean creation process and may be

-     * overridden to provide custom behavior like returning a cached bean

-     * instance.

-     * @param <T> The type of object to create

-     * @param c The Class to create an object from.

-     * @return A newly created object of the Class.

-     * @throws SQLException if creation failed.

-     */

-    protected <T> T newInstance(final Class<T> c) throws SQLException {

-        try {

-            return c.getDeclaredConstructor().newInstance();

-

-        } catch (final IllegalAccessException | InstantiationException | InvocationTargetException |

-            NoSuchMethodException e) {

-            throw new SQLException("Cannot create " + c.getName() + ": " + e.getMessage());

-        }

-    }

-

-    /**

-     * Initializes the fields of the provided bean from the ResultSet.

-     * @param <T> The type of bean

-     * @param resultSet The result set.

-     * @param bean The bean to be populated.

-     * @return An initialized object.

-     * @throws SQLException if a database error occurs.

-     */

-    public <T> T populateBean(final ResultSet resultSet, final T bean) throws SQLException {

-        final PropertyDescriptor[] props = propertyDescriptors(bean.getClass());

-        final ResultSetMetaData rsmd = resultSet.getMetaData();

-        final int[] columnToProperty = mapColumnsToProperties(rsmd, props);

-

-        return populateBean(resultSet, bean, props, columnToProperty);

-    }

-

-    /**

-     * This method populates a bean from the ResultSet based upon the underlying meta-data.

-     *

-     * @param <T> The type of bean

-     * @param resultSet The result set.

-     * @param bean The bean to be populated.

-     * @param props The property descriptors.

-     * @param columnToProperty The column indices in the result set.

-     * @return An initialized object.

-     * @throws SQLException if a database error occurs.

-     */

-    private <T> T populateBean(final ResultSet resultSet, final T bean,

-            final PropertyDescriptor[] props, final int[] columnToProperty)

-            throws SQLException {

-

-        for (int i = 1; i < columnToProperty.length; i++) {

-

-            if (columnToProperty[i] == PROPERTY_NOT_FOUND) {

-                continue;

-            }

-

-            final PropertyDescriptor prop = props[columnToProperty[i]];

-            final Class<?> propType = prop.getPropertyType();

-

-            Object value = null;

-            if (propType != null) {

-                value = processColumn(resultSet, i, propType);

-

-                if (value == null && propType.isPrimitive()) {

-                    value = PRIMITIVE_DEFAULTS.get(propType);

-                }

-            }

-

-            callSetter(bean, prop, value);

-        }

-

-        return bean;

-    }

-

-    /**

-     * Convert a {@code ResultSet} column into an object.  Simple

-     * implementations could just call {@code rs.getObject(index)} while

-     * more complex implementations could perform type manipulation to match

-     * the column's type to the bean property type.

-     *

-     * <p>

-     * This implementation calls the appropriate {@code ResultSet} getter

-     * method for the given property type to perform the type conversion.  If

-     * the property type doesn't match one of the supported

-     * {@code ResultSet} types, {@code getObject} is called.

-     * </p>

-     *

-     * @param resultSet The {@code ResultSet} currently being processed.  It is

-     * positioned on a valid row before being passed into this method.

-     *

-     * @param index The current column index being processed.

-     * @param propType The bean property type that this column needs to be

-     * converted into.

-     *

-     * @throws SQLException if a database access error occurs

-     * @return The object from the {@code ResultSet} at the given column

-     * index after optional type processing or {@code null} if the column

-     * value was SQL NULL.

-     */

-    protected Object processColumn(final ResultSet resultSet, final int index, final Class<?> propType)

-        throws SQLException {

-

-        Object retval = resultSet.getObject(index);

-

-        if ( !propType.isPrimitive() && retval == null ) {

-            return null;

-        }

-

-        for (final ColumnHandler<?> handler : COLUMN_HANDLERS) {

-            if (handler.match(propType)) {

-                retval = handler.apply(resultSet, index);

-                break;

-            }

-        }

-

-        return retval;

-

-    }

-

-    /**

-     * Returns a PropertyDescriptor[] for the given Class.

-     *

-     * @param c The Class to retrieve PropertyDescriptors for.

-     * @return A PropertyDescriptor[] describing the Class.

-     * @throws SQLException if introspection failed.

-     */

-    private PropertyDescriptor[] propertyDescriptors(final Class<?> c)

-        throws SQLException {

-        // Introspector caches BeanInfo classes for better performance

-        BeanInfo beanInfo = null;

-        try {

-            beanInfo = Introspector.getBeanInfo(c);

-

-        } catch (final IntrospectionException e) {

-            throw new SQLException(

-                "Bean introspection failed: " + e.getMessage());

-        }

-

-        return beanInfo.getPropertyDescriptors();

-    }

-

-    /**

-     * Convert a {@code ResultSet} row into a JavaBean.  This

-     * implementation uses reflection and {@code BeanInfo} classes to

-     * match column names to bean property names.  Properties are matched to

-     * columns based on several factors:

-     * &lt;br/&gt;

-     * &lt;ol&gt;

-     *     &lt;li&gt;

-     *     The class has a writable property with the same name as a column.

-     *     The name comparison is case insensitive.

-     *     &lt;/li&gt;

-     *

-     *     &lt;li&gt;

-     *     The column type can be converted to the property's set method

-     *     parameter type with a ResultSet.get* method.  If the conversion fails

-     *     (ie. the property was an int and the column was a Timestamp) an

-     *     SQLException is thrown.

-     *     &lt;/li&gt;

-     * &lt;/ol&gt;

-     *

-     * &lt;p&gt;

-     * Primitive bean properties are set to their defaults when SQL NULL is

-     * returned from the {@code ResultSet}.  Numeric fields are set to 0

-     * and booleans are set to false.  Object bean properties are set to

-     * {@code null} when SQL NULL is returned.  This is the same behavior

-     * as the {@code ResultSet} get* methods.

-     * &lt;/p&gt;

-     * @param <T> The type of bean to create

-     * @param rs ResultSet that supplies the bean data

-     * @param type Class from which to create the bean instance

-     * @throws SQLException if a database access error occurs

-     * @return the newly created bean

-     */

-    public <T> T toBean(final ResultSet rs, final Class<? extends T> type) throws SQLException {

-        final T bean = this.newInstance(type);

-        return this.populateBean(rs, bean);

-    }

-

-    /**

-     * Convert a {@code ResultSet} into a {@code List} of JavaBeans.

-     * This implementation uses reflection and {@code BeanInfo} classes to

-     * match column names to bean property names. Properties are matched to

-     * columns based on several factors:

-     * &lt;br/&gt;

-     * &lt;ol&gt;

-     *     &lt;li&gt;

-     *     The class has a writable property with the same name as a column.

-     *     The name comparison is case insensitive.

-     *     &lt;/li&gt;

-     *

-     *     &lt;li&gt;

-     *     The column type can be converted to the property's set method

-     *     parameter type with a ResultSet.get* method.  If the conversion fails

-     *     (ie. the property was an int and the column was a Timestamp) an

-     *     SQLException is thrown.

-     *     &lt;/li&gt;

-     * &lt;/ol&gt;

-     *

-     * <p>

-     * Primitive bean properties are set to their defaults when SQL NULL is

-     * returned from the {@code ResultSet}.  Numeric fields are set to 0

-     * and booleans are set to false.  Object bean properties are set to

-     * {@code null} when SQL NULL is returned.  This is the same behavior

-     * as the {@code ResultSet} get* methods.

-     * &lt;/p&gt;

-     * @param <T> The type of bean to create

-     * @param resultSet ResultSet that supplies the bean data

-     * @param type Class from which to create the bean instance

-     * @throws SQLException if a database access error occurs

-     * @return the newly created List of beans

-     */

-    public <T> List<T> toBeanList(final ResultSet resultSet, final Class<? extends T> type) throws SQLException {

-        final List<T> results = new ArrayList<>();

-        if (!resultSet.next()) {

-            return results;

-        }

-        final PropertyDescriptor[] props = propertyDescriptors(type);

-        final ResultSetMetaData rsmd = resultSet.getMetaData();

-        final int[] columnToProperty = mapColumnsToProperties(rsmd, props);

-        do {

-            results.add(this.createBean(resultSet, type, props, columnToProperty));

-        } while (resultSet.next()); // NOPMD False positive CheckResultSet

-        return results;

-    }

-

-}

+/*
+ * 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.dbutils;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+/**
+ * <p>
+ * {@code BeanProcessor} matches column names to bean property names
+ * and converts {@code ResultSet} columns into objects for those bean
+ * properties.  Subclasses should override the methods in the processing chain
+ * to customize behavior.
+ * </p>
+ *
+ * <p>
+ * This class is thread-safe.
+ * </p>
+ *
+ * @see BasicRowProcessor
+ * @since 1.1
+ */
+public class BeanProcessor {
+
+    /**
+     * Special array value used by {@code mapColumnsToProperties} that
+     * indicates there is no bean property that matches a column from a
+     * {@code ResultSet}.
+     */
+    protected static final int PROPERTY_NOT_FOUND = -1;
+
+    /**
+     * Set a bean's primitive properties to these defaults when SQL NULL
+     * is returned.  These are the same as the defaults that ResultSet get*
+     * methods return in the event of a NULL column.
+     */
+    private static final Map<Class<?>, Object> PRIMITIVE_DEFAULTS = new HashMap<>();
+
+    private static final List<ColumnHandler<?>> COLUMN_HANDLERS = new ArrayList<>();
+
+    private static final List<PropertyHandler> PROPERTY_HANDLERS = new ArrayList<>();
+
+    static {
+        PRIMITIVE_DEFAULTS.put(Integer.TYPE, Integer.valueOf(0));
+        PRIMITIVE_DEFAULTS.put(Short.TYPE, Short.valueOf((short) 0));
+        PRIMITIVE_DEFAULTS.put(Byte.TYPE, Byte.valueOf((byte) 0));
+        PRIMITIVE_DEFAULTS.put(Float.TYPE, Float.valueOf(0f));
+        PRIMITIVE_DEFAULTS.put(Double.TYPE, Double.valueOf(0d));
+        PRIMITIVE_DEFAULTS.put(Long.TYPE, Long.valueOf(0L));
+        PRIMITIVE_DEFAULTS.put(Boolean.TYPE, Boolean.FALSE);
+        PRIMITIVE_DEFAULTS.put(Character.TYPE, Character.valueOf((char) 0));
+
+        // Use a ServiceLoader to find implementations
+        ServiceLoader.load(ColumnHandler.class).forEach(COLUMN_HANDLERS::add);
+
+        // Use a ServiceLoader to find implementations
+        ServiceLoader.load(PropertyHandler.class).forEach(PROPERTY_HANDLERS::add);
+    }
+
+    /**
+     * ResultSet column to bean property name overrides.
+     */
+    private final Map<String, String> columnToPropertyOverrides;
+
+    /**
+     * Constructor for BeanProcessor.
+     */
+    public BeanProcessor() {
+        this(new HashMap<>());
+    }
+
+    /**
+     * Constructor for BeanProcessor configured with column to property name overrides.
+     *
+     * @param columnToPropertyOverrides ResultSet column to bean property name overrides
+     * @since 1.5
+     */
+    public BeanProcessor(final Map<String, String> columnToPropertyOverrides) {
+        if (columnToPropertyOverrides == null) {
+            throw new IllegalArgumentException("columnToPropertyOverrides map cannot be null");
+        }
+        this.columnToPropertyOverrides = columnToPropertyOverrides;
+    }
+
+    /**
+     * Calls the setter method on the target object for the given property.
+     * If no setter method exists for the property, this method does nothing.
+     * @param target The object to set the property on.
+     * @param prop The property to set.
+     * @param value The value to pass into the setter.
+     * @throws SQLException if an error occurs setting the property.
+     */
+    private void callSetter(final Object target, final PropertyDescriptor prop, Object value)
+            throws SQLException {
+
+        final Method setter = getWriteMethod(target, prop, value);
+
+        if (setter == null || setter.getParameterTypes().length != 1) {
+            return;
+        }
+
+        try {
+            final Class<?> firstParam = setter.getParameterTypes()[0];
+            for (final PropertyHandler handler : PROPERTY_HANDLERS) {
+                if (handler.match(firstParam, value)) {
+                    value = handler.apply(firstParam, value);
+                    break;
+                }
+            }
+
+            // Don't call setter if the value object isn't the right type
+            if (!isCompatibleType(value, firstParam)) {
+                throw new SQLException(
+                        "Cannot set " + prop.getName() + ": incompatible types, cannot convert " + value.getClass().getName() + " to " + firstParam.getName());
+                // value cannot be null here because isCompatibleType allows null
+            }
+            setter.invoke(target, value);
+
+        } catch (final IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
+            throw new SQLException("Cannot set " + prop.getName() + ": " + e.getMessage());
+        }
+    }
+
+    /**
+     * Creates a new object and initializes its fields from the ResultSet.
+     * @param <T> The type of bean to create
+     * @param resultSet The result set.
+     * @param type The bean type (the return type of the object).
+     * @param props The property descriptors.
+     * @param columnToProperty The column indices in the result set.
+     * @return An initialized object.
+     * @throws SQLException if a database error occurs.
+     */
+    private <T> T createBean(final ResultSet resultSet, final Class<T> type, final PropertyDescriptor[] props, final int[] columnToProperty)
+            throws SQLException {
+        return populateBean(resultSet, this.newInstance(type), props, columnToProperty);
+    }
+
+    /** Does nothing. */
+    @Override
+    protected final void finalize() {
+        // SpotBugs CT_CONSTRUCTOR_THROW
+    }
+
+    /**
+     * Gets the write method to use when setting {@code value} to the {@code target}.
+     *
+     * @param target Object where the write method will be called.
+     * @param prop   BeanUtils information.
+     * @param value  The value that will be passed to the write method.
+     * @return The {@link java.lang.reflect.Method} to call on {@code target} to write {@code value} or {@code null} if
+     *         there is no suitable write method.
+     */
+    protected Method getWriteMethod(final Object target, final PropertyDescriptor prop, final Object value) {
+        return prop.getWriteMethod();
+    }
+
+    /**
+     * ResultSet.getObject() returns an Integer object for an INT column.  The
+     * setter method for the property might take an Integer or a primitive int.
+     * This method returns true if the value can be successfully passed into
+     * the setter method.  Remember, Method.invoke() handles the unwrapping
+     * of Integer into an int.
+     *
+     * @param value The value to be passed into the setter method.
+     * @param type The setter's parameter type (non-null)
+     * @return boolean True if the value is compatible (null => true)
+     */
+    private boolean isCompatibleType(final Object value, final Class<?> type) {
+        // Do object check first, then primitives
+        return value == null || type.isInstance(value) || matchesPrimitive(type, value.getClass());
+    }
+
+    /**
+     * The positions in the returned array represent column numbers.  The
+     * values stored at each position represent the index in the
+     * {@code PropertyDescriptor[]} for the bean property that matches
+     * the column name.  If no bean property was found for a column, the
+     * position is set to {@code PROPERTY_NOT_FOUND}.
+     *
+     * @param rsmd The {@code ResultSetMetaData} containing column
+     * information.
+     *
+     * @param props The bean property descriptors.
+     * @throws SQLException if a database access error occurs
+     * @return An int[] with column index to property index mappings.  The 0th
+     * element is meaningless because JDBC column indexing starts at 1.
+     */
+    protected int[] mapColumnsToProperties(final ResultSetMetaData rsmd,
+            final PropertyDescriptor[] props) throws SQLException {
+
+        final int cols = rsmd.getColumnCount();
+        final int[] columnToProperty = new int[cols + 1];
+        Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);
+
+        for (int col = 1; col <= cols; col++) {
+            String columnName = rsmd.getColumnLabel(col);
+            if (null == columnName || 0 == columnName.length()) {
+              columnName = rsmd.getColumnName(col);
+            }
+            String propertyName = columnToPropertyOverrides.get(columnName);
+            if (propertyName == null) {
+                propertyName = columnName;
+            }
+            if (propertyName == null) {
+                propertyName = Integer.toString(col);
+            }
+
+            for (int i = 0; i < props.length; i++) {
+                final PropertyDescriptor prop = props[i];
+                final Method reader = prop.getReadMethod();
+
+                // Check for @Column annotations as explicit marks
+                final Column column;
+                if (reader != null) {
+                    column = reader.getAnnotation(Column.class);
+                } else {
+                    column = null;
+                }
+
+                final String propertyColumnName;
+                if (column != null) {
+                    propertyColumnName = column.name();
+                } else {
+                    propertyColumnName = prop.getName();
+                }
+                if (propertyName.equalsIgnoreCase(propertyColumnName)) {
+                    columnToProperty[col] = i;
+                    break;
+                }
+            }
+        }
+
+        return columnToProperty;
+    }
+
+    /**
+     * Check whether a value is of the same primitive type as {@code targetType}.
+     *
+     * @param targetType The primitive type to target.
+     * @param valueType The value to match to the primitive type.
+     * @return Whether {@code valueType} can be coerced (e.g. autoboxed) into {@code targetType}.
+     */
+    private boolean matchesPrimitive(final Class<?> targetType, final Class<?> valueType) {
+        if (!targetType.isPrimitive()) {
+            return false;
+        }
+
+        try {
+            // see if there is a "TYPE" field.  This is present for primitive wrappers.
+            final Field typeField = valueType.getField("TYPE");
+            final Object primitiveValueType = typeField.get(valueType);
+
+            if (targetType == primitiveValueType) {
+                return true;
+            }
+        } catch (final NoSuchFieldException | IllegalAccessException ignored) {
+            // an inaccessible TYPE field is a good sign that we're not working with a primitive wrapper.
+            // nothing to do.  we can't match for compatibility
+        }
+        return false;
+    }
+
+    /**
+     * Factory method that returns a new instance of the given Class.  This
+     * is called at the start of the bean creation process and may be
+     * overridden to provide custom behavior like returning a cached bean
+     * instance.
+     * @param <T> The type of object to create
+     * @param c The Class to create an object from.
+     * @return A newly created object of the Class.
+     * @throws SQLException if creation failed.
+     */
+    protected <T> T newInstance(final Class<T> c) throws SQLException {
+        try {
+            return c.getDeclaredConstructor().newInstance();
+
+        } catch (final IllegalAccessException | InstantiationException | InvocationTargetException |
+            NoSuchMethodException e) {
+            throw new SQLException("Cannot create " + c.getName() + ": " + e.getMessage());
+        }
+    }
+
+    /**
+     * Initializes the fields of the provided bean from the ResultSet.
+     * @param <T> The type of bean
+     * @param resultSet The result set.
+     * @param bean The bean to be populated.
+     * @return An initialized object.
+     * @throws SQLException if a database error occurs.
+     */
+    public <T> T populateBean(final ResultSet resultSet, final T bean) throws SQLException {
+        final PropertyDescriptor[] props = propertyDescriptors(bean.getClass());
+        final ResultSetMetaData rsmd = resultSet.getMetaData();
+        final int[] columnToProperty = mapColumnsToProperties(rsmd, props);
+
+        return populateBean(resultSet, bean, props, columnToProperty);
+    }
+
+    /**
+     * This method populates a bean from the ResultSet based upon the underlying meta-data.
+     *
+     * @param <T> The type of bean
+     * @param resultSet The result set.
+     * @param bean The bean to be populated.
+     * @param props The property descriptors.
+     * @param columnToProperty The column indices in the result set.
+     * @return An initialized object.
+     * @throws SQLException if a database error occurs.
+     */
+    private <T> T populateBean(final ResultSet resultSet, final T bean,
+            final PropertyDescriptor[] props, final int[] columnToProperty)
+            throws SQLException {
+
+        for (int i = 1; i < columnToProperty.length; i++) {
+
+            if (columnToProperty[i] == PROPERTY_NOT_FOUND) {
+                continue;
+            }
+
+            final PropertyDescriptor prop = props[columnToProperty[i]];
+            final Class<?> propType = prop.getPropertyType();
+
+            Object value = null;
+            if (propType != null) {
+                value = processColumn(resultSet, i, propType);
+
+                if (value == null && propType.isPrimitive()) {
+                    value = PRIMITIVE_DEFAULTS.get(propType);
+                }
+            }
+
+            callSetter(bean, prop, value);
+        }
+
+        return bean;
+    }
+
+    /**
+     * Convert a {@code ResultSet} column into an object.  Simple
+     * implementations could just call {@code rs.getObject(index)} while
+     * more complex implementations could perform type manipulation to match
+     * the column's type to the bean property type.
+     *
+     * <p>
+     * This implementation calls the appropriate {@code ResultSet} getter
+     * method for the given property type to perform the type conversion.  If
+     * the property type doesn't match one of the supported
+     * {@code ResultSet} types, {@code getObject} is called.
+     * </p>
+     *
+     * @param resultSet The {@code ResultSet} currently being processed.  It is
+     * positioned on a valid row before being passed into this method.
+     *
+     * @param index The current column index being processed.
+     * @param propType The bean property type that this column needs to be
+     * converted into.
+     *
+     * @throws SQLException if a database access error occurs
+     * @return The object from the {@code ResultSet} at the given column
+     * index after optional type processing or {@code null} if the column
+     * value was SQL NULL.
+     */
+    protected Object processColumn(final ResultSet resultSet, final int index, final Class<?> propType)
+        throws SQLException {
+
+        Object retval = resultSet.getObject(index);
+
+        if ( !propType.isPrimitive() && retval == null ) {
+            return null;
+        }
+
+        for (final ColumnHandler<?> handler : COLUMN_HANDLERS) {
+            if (handler.match(propType)) {
+                retval = handler.apply(resultSet, index);
+                break;
+            }
+        }
+
+        return retval;
+
+    }
+
+    /**
+     * Returns a PropertyDescriptor[] for the given Class.
+     *
+     * @param c The Class to retrieve PropertyDescriptors for.
+     * @return A PropertyDescriptor[] describing the Class.
+     * @throws SQLException if introspection failed.
+     */
+    private PropertyDescriptor[] propertyDescriptors(final Class<?> c)
+        throws SQLException {
+        // Introspector caches BeanInfo classes for better performance
+        BeanInfo beanInfo = null;
+        try {
+            beanInfo = Introspector.getBeanInfo(c);
+
+        } catch (final IntrospectionException e) {
+            throw new SQLException(
+                "Bean introspection failed: " + e.getMessage());
+        }
+
+        return beanInfo.getPropertyDescriptors();
+    }
+
+    /**
+     * Convert a {@code ResultSet} row into a JavaBean.  This
+     * implementation uses reflection and {@code BeanInfo} classes to
+     * match column names to bean property names.  Properties are matched to
+     * columns based on several factors:
+     * &lt;br/&gt;
+     * &lt;ol&gt;
+     *     &lt;li&gt;
+     *     The class has a writable property with the same name as a column.
+     *     The name comparison is case insensitive.
+     *     &lt;/li&gt;
+     *
+     *     &lt;li&gt;
+     *     The column type can be converted to the property's set method
+     *     parameter type with a ResultSet.get* method.  If the conversion fails
+     *     (ie. the property was an int and the column was a Timestamp) an
+     *     SQLException is thrown.
+     *     &lt;/li&gt;
+     * &lt;/ol&gt;
+     *
+     * &lt;p&gt;
+     * Primitive bean properties are set to their defaults when SQL NULL is
+     * returned from the {@code ResultSet}.  Numeric fields are set to 0
+     * and booleans are set to false.  Object bean properties are set to
+     * {@code null} when SQL NULL is returned.  This is the same behavior
+     * as the {@code ResultSet} get* methods.
+     * &lt;/p&gt;
+     * @param <T> The type of bean to create
+     * @param rs ResultSet that supplies the bean data
+     * @param type Class from which to create the bean instance
+     * @throws SQLException if a database access error occurs
+     * @return the newly created bean
+     */
+    public <T> T toBean(final ResultSet rs, final Class<? extends T> type) throws SQLException {
+        final T bean = this.newInstance(type);
+        return this.populateBean(rs, bean);
+    }
+
+    /**
+     * Convert a {@code ResultSet} into a {@code List} of JavaBeans.
+     * This implementation uses reflection and {@code BeanInfo} classes to
+     * match column names to bean property names. Properties are matched to
+     * columns based on several factors:
+     * &lt;br/&gt;
+     * &lt;ol&gt;
+     *     &lt;li&gt;
+     *     The class has a writable property with the same name as a column.
+     *     The name comparison is case insensitive.
+     *     &lt;/li&gt;
+     *
+     *     &lt;li&gt;
+     *     The column type can be converted to the property's set method
+     *     parameter type with a ResultSet.get* method.  If the conversion fails
+     *     (ie. the property was an int and the column was a Timestamp) an
+     *     SQLException is thrown.
+     *     &lt;/li&gt;
+     * &lt;/ol&gt;
+     *
+     * <p>
+     * Primitive bean properties are set to their defaults when SQL NULL is
+     * returned from the {@code ResultSet}.  Numeric fields are set to 0
+     * and booleans are set to false.  Object bean properties are set to
+     * {@code null} when SQL NULL is returned.  This is the same behavior
+     * as the {@code ResultSet} get* methods.
+     * &lt;/p&gt;
+     * @param <T> The type of bean to create
+     * @param resultSet ResultSet that supplies the bean data
+     * @param type Class from which to create the bean instance
+     * @throws SQLException if a database access error occurs
+     * @return the newly created List of beans
+     */
+    public <T> List<T> toBeanList(final ResultSet resultSet, final Class<? extends T> type) throws SQLException {
+        final List<T> results = new ArrayList<>();
+        if (!resultSet.next()) {
+            return results;
+        }
+        final PropertyDescriptor[] props = propertyDescriptors(type);
+        final ResultSetMetaData rsmd = resultSet.getMetaData();
+        final int[] columnToProperty = mapColumnsToProperties(rsmd, props);
+        do {
+            results.add(this.createBean(resultSet, type, props, columnToProperty));
+        } while (resultSet.next()); // NOPMD False positive CheckResultSet
+        return results;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/dbutils/ColumnHandler.java b/src/main/java/org/apache/commons/dbutils/ColumnHandler.java
index b5eccec..651aec9 100644
--- a/src/main/java/org/apache/commons/dbutils/ColumnHandler.java
+++ b/src/main/java/org/apache/commons/dbutils/ColumnHandler.java
@@ -1,47 +1,47 @@
-/*

- * 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.dbutils;

-

-import java.sql.ResultSet;

-import java.sql.SQLException;

-

-/**

- * Defines how to process columns when constructing a bean from a {@link ResultSet}. Instances do the work of retrieving data from a {@code ResultSet}.

- *

- * @param <T> The return type.

- */

-public interface ColumnHandler<T> {

-

-    /**

-     * Retrieves the current row's column value from a {@link ResultSet} and stores it into an instance of {@code propType}. This method is only called if

-     * {@link #match(Class)} returns true.

-     *

-     * @param resultSet   The source result set. This must be on the correct row.

-     * @param columnIndex The position of the column to retrieve, a 1-based index.

-     * @return The converted value or the original value if something doesn't work out.

-     * @throws SQLException if the columnIndex is not valid; if a database access error occurs or this method is called on a closed result set

-     */

-    T apply(ResultSet resultSet, int columnIndex) throws SQLException;

-

-    /**

-     * Tests whether to handle a column targeted for a value type matching {@code propType}.

-     *

-     * @param propType The type of the target parameter.

-     * @return true is this property handler handles this {@code propType}; false otherwise.

-     */

-    boolean match(Class<?> propType);

-}

+/*
+ * 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.dbutils;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * Defines how to process columns when constructing a bean from a {@link ResultSet}. Instances do the work of retrieving data from a {@code ResultSet}.
+ *
+ * @param <T> The return type.
+ */
+public interface ColumnHandler<T> {
+
+    /**
+     * Retrieves the current row's column value from a {@link ResultSet} and stores it into an instance of {@code propType}. This method is only called if
+     * {@link #match(Class)} returns true.
+     *
+     * @param resultSet   The source result set. This must be on the correct row.
+     * @param columnIndex The position of the column to retrieve, a 1-based index.
+     * @return The converted value or the original value if something doesn't work out.
+     * @throws SQLException if the columnIndex is not valid; if a database access error occurs or this method is called on a closed result set
+     */
+    T apply(ResultSet resultSet, int columnIndex) throws SQLException;
+
+    /**
+     * Tests whether to handle a column targeted for a value type matching {@code propType}.
+     *
+     * @param propType The type of the target parameter.
+     * @return true is this property handler handles this {@code propType}; false otherwise.
+     */
+    boolean match(Class<?> propType);
+}
diff --git a/src/main/java/org/apache/commons/dbutils/DbUtils.java b/src/main/java/org/apache/commons/dbutils/DbUtils.java
index 8efddac..e328e14 100644
--- a/src/main/java/org/apache/commons/dbutils/DbUtils.java
+++ b/src/main/java/org/apache/commons/dbutils/DbUtils.java
@@ -1,428 +1,428 @@
-/*

- * 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.dbutils;

-

-import static java.sql.DriverManager.registerDriver;

-

-import java.io.PrintWriter;

-import java.lang.reflect.Constructor;

-import java.sql.Connection;

-import java.sql.Driver;

-import java.sql.DriverPropertyInfo;

-import java.sql.ResultSet;

-import java.sql.SQLException;

-import java.sql.SQLFeatureNotSupportedException;

-import java.sql.Statement;

-import java.util.Properties;

-import java.util.logging.Logger;

-

-/**

- * A collection of JDBC helper methods.  This class is thread safe.

- */

-public final class DbUtils {

-

-    /**

-     * Simple {@link Driver} proxy class that proxies a JDBC Driver loaded dynamically.

-     *

-     * @since 1.6

-     */

-    static final class DriverProxy implements Driver {

-

-        /**

-         * The adapted JDBC Driver loaded dynamically.

-         */

-        private final Driver adapted;

-

-        /**

-         * Creates a new JDBC Driver that adapts a JDBC Driver loaded dynamically.

-         *

-         * @param adapted the adapted JDBC Driver loaded dynamically.

-         */

-        public DriverProxy(final Driver adapted) {

-            this.adapted = adapted;

-        }

-

-        /**

-         * {@inheritDoc}

-         */

-        @Override

-        public boolean acceptsURL(final String url) throws SQLException {

-            return adapted.acceptsURL(url);

-        }

-

-        /**

-         * {@inheritDoc}

-         */

-        @Override

-        public Connection connect(final String url, final Properties info) throws SQLException {

-            return adapted.connect(url, info);

-        }

-

-        /**

-         * {@inheritDoc}

-         */

-        @Override

-        public int getMajorVersion() {

-            return adapted.getMajorVersion();

-        }

-

-        /**

-         * {@inheritDoc}

-         */

-        @Override

-        public int getMinorVersion() {

-            return adapted.getMinorVersion();

-        }

-

-        /**

-         * Java 1.7 method.

-         */

-        @Override

-        public Logger getParentLogger() throws SQLFeatureNotSupportedException {

-            return adapted.getParentLogger();

-        }

-

-        /**

-         * {@inheritDoc}

-         */

-        @Override

-        public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) throws SQLException {

-            return adapted.getPropertyInfo(url, info);

-        }

-

-        /**

-         * {@inheritDoc}

-         */

-        @Override

-        public boolean jdbcCompliant() {

-            return adapted.jdbcCompliant();

-        }

-

-    }

-

-    /**

-     * Close a {@code Connection}, avoid closing if null.

-     *

-     * @param conn Connection to close.

-     * @throws SQLException if a database access error occurs

-     */

-    public static void close(final Connection conn) throws SQLException {

-        if (conn != null) {

-            conn.close();

-        }

-    }

-

-    /**

-     * Close a {@code ResultSet}, avoid closing if null.

-     *

-     * @param resultSet ResultSet to close.

-     * @throws SQLException if a database access error occurs

-     */

-    public static void close(final ResultSet resultSet) throws SQLException {

-        if (resultSet != null) {

-            resultSet.close();

-        }

-    }

-

-    /**

-     * Close a {@code Statement}, avoid closing if null.

-     *

-     * @param stmt Statement to close.

-     * @throws SQLException if a database access error occurs

-     */

-    public static void close(final Statement stmt) throws SQLException {

-        if (stmt != null) {

-            stmt.close();

-        }

-    }

-

-    /**

-     * Close a {@code Connection}, avoid closing if null and hide

-     * any SQLExceptions that occur.

-     *

-     * @param conn Connection to close.

-     */

-    public static void closeQuietly(final Connection conn) {

-        try {

-            close(conn);

-        } catch (final SQLException e) { // NOPMD

-            // quiet

-        }

-    }

-

-    /**

-     * Close a {@code Connection}, {@code Statement} and

-     * {@code ResultSet}.  Avoid closing if null and hide any

-     * SQLExceptions that occur.

-     *

-     * @param conn Connection to close.

-     * @param stmt Statement to close.

-     * @param rs ResultSet to close.

-     */

-    public static void closeQuietly(final Connection conn, final Statement stmt,

-            final ResultSet rs) {

-

-        try {

-            closeQuietly(rs);

-        } finally {

-            try {

-                closeQuietly(stmt);

-            } finally {

-                closeQuietly(conn);

-            }

-        }

-

-    }

-

-    /**

-     * Close a {@code ResultSet}, avoid closing if null and hide any

-     * SQLExceptions that occur.

-     *

-     * @param resultSet ResultSet to close.

-     */

-    public static void closeQuietly(final ResultSet resultSet) {

-        try {

-            close(resultSet);

-        } catch (final SQLException e) { // NOPMD

-            // quiet

-        }

-    }

-

-    /**

-     * Close a {@code Statement}, avoid closing if null and hide

-     * any SQLExceptions that occur.

-     *

-     * @param stmt Statement to close.

-     */

-    public static void closeQuietly(final Statement stmt) {

-        try {

-            close(stmt);

-        } catch (final SQLException e) { // NOPMD

-            // quiet

-        }

-    }

-

-    /**

-     * Commits a {@code Connection} then closes it, avoid closing if null.

-     *

-     * @param conn Connection to close.

-     * @throws SQLException if a database access error occurs

-     */

-    public static void commitAndClose(final Connection conn) throws SQLException {

-        if (conn != null) {

-            try {

-                conn.commit();

-            } finally {

-                conn.close();

-            }

-        }

-    }

-

-    /**

-     * Commits a {@code Connection} then closes it, avoid closing if null

-     * and hide any SQLExceptions that occur.

-     *

-     * @param conn Connection to close.

-     */

-    public static void commitAndCloseQuietly(final Connection conn) {

-        try {

-            commitAndClose(conn);

-        } catch (final SQLException e) { // NOPMD

-            // quiet

-        }

-    }

-

-    /**

-     * Loads and registers a database driver class.

-     * If this succeeds, it returns true, else it returns false.

-     *

-     * @param classLoader the class loader used to load the driver class

-     * @param driverClassName of driver to load

-     * @return boolean {@code true} if the driver was found, otherwise {@code false}

-     * @since 1.4

-     */

-    public static boolean loadDriver(final ClassLoader classLoader, final String driverClassName) {

-        try {

-            final Class<?> loadedClass = classLoader.loadClass(driverClassName);

-

-            if (!Driver.class.isAssignableFrom(loadedClass)) {

-                return false;

-            }

-

-            @SuppressWarnings("unchecked") // guarded by previous check

-            final

-            Class<Driver> driverClass = (Class<Driver>) loadedClass;

-            final Constructor<Driver> driverConstructor = driverClass.getConstructor();

-

-            // make Constructor accessible if it is private

-            @SuppressWarnings("deprecation")

-            // TODO This is deprecated in Java9 and canAccess() should be used. Adding suppression for building on

-            //      later JDKs without a warning.

-            final boolean isConstructorAccessible = driverConstructor.isAccessible();

-            if (!isConstructorAccessible) {

-                driverConstructor.setAccessible(true);

-            }

-

-            try {

-                final Driver driver = driverConstructor.newInstance();

-                registerDriver(new DriverProxy(driver));

-            } finally {

-                driverConstructor.setAccessible(isConstructorAccessible);

-            }

-

-            return true;

-        } catch (final Exception e) {

-            return false;

-        }

-    }

-

-    /**

-     * Loads and registers a database driver class.

-     * If this succeeds, it returns true, else it returns false.

-     *

-     * @param driverClassName of driver to load

-     * @return boolean {@code true} if the driver was found, otherwise {@code false}

-     */

-    public static boolean loadDriver(final String driverClassName) {

-        return loadDriver(DbUtils.class.getClassLoader(), driverClassName);

-    }

-

-    /**

-     * Print the stack trace for a SQLException to STDERR.

-     *

-     * @param e SQLException to print stack trace of

-     */

-    public static void printStackTrace(final SQLException e) {

-        printStackTrace(e, new PrintWriter(System.err));

-    }

-

-    /**

-     * Print the stack trace for a SQLException to a

-     * specified PrintWriter.

-     *

-     * @param e SQLException to print stack trace of

-     * @param pw PrintWriter to print to

-     */

-    public static void printStackTrace(final SQLException e, final PrintWriter pw) {

-

-        SQLException next = e;

-        while (next != null) {

-            next.printStackTrace(pw);

-            next = next.getNextException();

-            if (next != null) {

-                pw.println("Next SQLException:");

-            }

-        }

-    }

-

-    /**

-     * Print warnings on a Connection to STDERR.

-     *

-     * @param conn Connection to print warnings from

-     */

-    public static void printWarnings(final Connection conn) {

-        printWarnings(conn, new PrintWriter(System.err));

-    }

-

-    /**

-     * Print warnings on a Connection to a specified PrintWriter.

-     *

-     * @param conn Connection to print warnings from

-     * @param pw PrintWriter to print to

-     */

-    public static void printWarnings(final Connection conn, final PrintWriter pw) {

-        if (conn != null) {

-            try {

-                printStackTrace(conn.getWarnings(), pw);

-            } catch (final SQLException e) {

-                printStackTrace(e, pw);

-            }

-        }

-    }

-

-    /**

-     * Rollback any changes made on the given connection.

-     * @param conn Connection to rollback.  A null value is legal.

-     * @throws SQLException if a database access error occurs

-     */

-    public static void rollback(final Connection conn) throws SQLException {

-        if (conn != null) {

-            conn.rollback();

-        }

-    }

-

-    /**

-     * Performs a rollback on the {@code Connection} then closes it,

-     * avoid closing if null.

-     *

-     * @param conn Connection to rollback.  A null value is legal.

-     * @throws SQLException if a database access error occurs

-     * @since 1.1

-     */

-    public static void rollbackAndClose(final Connection conn) throws SQLException {

-        if (conn != null) {

-            try {

-                conn.rollback();

-            } finally {

-                conn.close();

-            }

-        }

-    }

-

-    /**

-     * Performs a rollback on the {@code Connection} then closes it,

-     * avoid closing if null and hide any SQLExceptions that occur.

-     *

-     * @param conn Connection to rollback.  A null value is legal.

-     * @since 1.1

-     */

-    public static void rollbackAndCloseQuietly(final Connection conn) {

-        try {

-            rollbackAndClose(conn);

-        } catch (final SQLException e) { // NOPMD

-            // quiet

-        }

-    }

-

-    /**

-     * Performs a rollback on the {@code Connection}, avoid

-     * closing if null and hide any SQLExceptions that occur.

-     *

-     * @param conn Connection to rollback.  A null value is legal.

-     * @since DbUtils 2.0

-     */

-    public static void rollbackQuietly(final Connection conn) {

-        try {

-            rollback(conn);

-        } catch (final SQLException e) { // NOPMD

-            // quiet

-        }

-    }

-

-    /**

-     * Default constructor.

-     *

-     * Utility classes should not have a public or default constructor,

-     * but this one preserves retro-compatibility.

-     *

-     * @since 1.4

-     */

-    public DbUtils() {

-        // do nothing

-    }

-

-}

+/*
+ * 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.dbutils;
+
+import static java.sql.DriverManager.registerDriver;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.DriverPropertyInfo;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.Statement;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+/**
+ * A collection of JDBC helper methods.  This class is thread safe.
+ */
+public final class DbUtils {
+
+    /**
+     * Simple {@link Driver} proxy class that proxies a JDBC Driver loaded dynamically.
+     *
+     * @since 1.6
+     */
+    static final class DriverProxy implements Driver {
+
+        /**
+         * The adapted JDBC Driver loaded dynamically.
+         */
+        private final Driver adapted;
+
+        /**
+         * Creates a new JDBC Driver that adapts a JDBC Driver loaded dynamically.
+         *
+         * @param adapted the adapted JDBC Driver loaded dynamically.
+         */
+        public DriverProxy(final Driver adapted) {
+            this.adapted = adapted;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean acceptsURL(final String url) throws SQLException {
+            return adapted.acceptsURL(url);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public Connection connect(final String url, final Properties info) throws SQLException {
+            return adapted.connect(url, info);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int getMajorVersion() {
+            return adapted.getMajorVersion();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int getMinorVersion() {
+            return adapted.getMinorVersion();
+        }
+
+        /**
+         * Java 1.7 method.
+         */
+        @Override
+        public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+            return adapted.getParentLogger();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) throws SQLException {
+            return adapted.getPropertyInfo(url, info);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean jdbcCompliant() {
+            return adapted.jdbcCompliant();
+        }
+
+    }
+
+    /**
+     * Close a {@code Connection}, avoid closing if null.
+     *
+     * @param conn Connection to close.
+     * @throws SQLException if a database access error occurs
+     */
+    public static void close(final Connection conn) throws SQLException {
+        if (conn != null) {
+            conn.close();
+        }
+    }
+
+    /**
+     * Close a {@code ResultSet}, avoid closing if null.
+     *
+     * @param resultSet ResultSet to close.
+     * @throws SQLException if a database access error occurs
+     */
+    public static void close(final ResultSet resultSet) throws SQLException {
+        if (resultSet != null) {
+            resultSet.close();
+        }
+    }
+
+    /**
+     * Close a {@code Statement}, avoid closing if null.
+     *
+     * @param stmt Statement to close.
+     * @throws SQLException if a database access error occurs
+     */
+    public static void close(final Statement stmt) throws SQLException {
+        if (stmt != null) {
+            stmt.close();
+        }
+    }
+
+    /**
+     * Close a {@code Connection}, avoid closing if null and hide
+     * any SQLExceptions that occur.
+     *
+     * @param conn Connection to close.
+     */
+    public static void closeQuietly(final Connection conn) {
+        try {
+            close(conn);
+        } catch (final SQLException e) { // NOPMD
+            // quiet
+        }
+    }
+
+    /**
+     * Close a {@code Connection}, {@code Statement} and
+     * {@code ResultSet}.  Avoid closing if null and hide any
+     * SQLExceptions that occur.
+     *
+     * @param conn Connection to close.
+     * @param stmt Statement to close.
+     * @param rs ResultSet to close.
+     */
+    public static void closeQuietly(final Connection conn, final Statement stmt,
+            final ResultSet rs) {
+
+        try {
+            closeQuietly(rs);
+        } finally {
+            try {
+                closeQuietly(stmt);
+            } finally {
+                closeQuietly(conn);
+            }
+        }
+
+    }
+
+    /**
+     * Close a {@code ResultSet}, avoid closing if null and hide any
+     * SQLExceptions that occur.
+     *
+     * @param resultSet ResultSet to close.
+     */
+    public static void closeQuietly(final ResultSet resultSet) {
+        try {
+            close(resultSet);
+        } catch (final SQLException e) { // NOPMD
+            // quiet
+        }
+    }
+
+    /**
+     * Close a {@code Statement}, avoid closing if null and hide
+     * any SQLExceptions that occur.
+     *
+     * @param stmt Statement to close.
+     */
+    public static void closeQuietly(final Statement stmt) {
+        try {
+            close(stmt);
+        } catch (final SQLException e) { // NOPMD
+            // quiet
+        }
+    }
+
+    /**
+     * Commits a {@code Connection} then closes it, avoid closing if null.
+     *
+     * @param conn Connection to close.
+     * @throws SQLException if a database access error occurs
+     */
+    public static void commitAndClose(final Connection conn) throws SQLException {
+        if (conn != null) {
+            try {
+                conn.commit();
+            } finally {
+                conn.close();
+            }
+        }
+    }
+
+    /**
+     * Commits a {@code Connection} then closes it, avoid closing if null
+     * and hide any SQLExceptions that occur.
+     *
+     * @param conn Connection to close.
+     */
+    public static void commitAndCloseQuietly(final Connection conn) {
+        try {
+            commitAndClose(conn);
+        } catch (final SQLException e) { // NOPMD
+            // quiet
+        }
+    }
+
+    /**
+     * Loads and registers a database driver class.
+     * If this succeeds, it returns true, else it returns false.
+     *
+     * @param classLoader the class loader used to load the driver class
+     * @param driverClassName of driver to load
+     * @return boolean {@code true} if the driver was found, otherwise {@code false}
+     * @since 1.4
+     */
+    public static boolean loadDriver(final ClassLoader classLoader, final String driverClassName) {
+        try {
+            final Class<?> loadedClass = classLoader.loadClass(driverClassName);
+
+            if (!Driver.class.isAssignableFrom(loadedClass)) {
+                return false;
+            }
+
+            @SuppressWarnings("unchecked") // guarded by previous check
+            final
+            Class<Driver> driverClass = (Class<Driver>) loadedClass;
+            final Constructor<Driver> driverConstructor = driverClass.getConstructor();
+
+            // make Constructor accessible if it is private
+            @SuppressWarnings("deprecation")
+            // TODO This is deprecated in Java9 and canAccess() should be used. Adding suppression for building on
+            //      later JDKs without a warning.
+            final boolean isConstructorAccessible = driverConstructor.isAccessible();
+            if (!isConstructorAccessible) {
+                driverConstructor.setAccessible(true);
+            }
+
+            try {
+                final Driver driver = driverConstructor.newInstance();
+                registerDriver(new DriverProxy(driver));
+            } finally {
+                driverConstructor.setAccessible(isConstructorAccessible);
+            }
+
+            return true;
+        } catch (final Exception e) {
+            return false;
+        }
+    }
+
+    /**
+     * Loads and registers a database driver class.
+     * If this succeeds, it returns true, else it returns false.
+     *
+     * @param driverClassName of driver to load
+     * @return boolean {@code true} if the driver was found, otherwise {@code false}
+     */
+    public static boolean loadDriver(final String driverClassName) {
+        return loadDriver(DbUtils.class.getClassLoader(), driverClassName);
+    }
+
+    /**
+     * Print the stack trace for a SQLException to STDERR.
+     *
+     * @param e SQLException to print stack trace of
+     */
+    public static void printStackTrace(final SQLException e) {
+        printStackTrace(e, new PrintWriter(System.err));
+    }
+
+    /**
+     * Print the stack trace for a SQLException to a
+     * specified PrintWriter.
+     *
+     * @param e SQLException to print stack trace of
+     * @param pw PrintWriter to print to
+     */
+    public static void printStackTrace(final SQLException e, final PrintWriter pw) {
+
+        SQLException next = e;
+        while (next != null) {
+            next.printStackTrace(pw);
+            next = next.getNextException();
+            if (next != null) {
+                pw.println("Next SQLException:");
+            }
+        }
+    }
+
+    /**
+     * Print warnings on a Connection to STDERR.
+     *
+     * @param conn Connection to print warnings from
+     */
+    public static void printWarnings(final Connection conn) {
+        printWarnings(conn, new PrintWriter(System.err));
+    }
+
+    /**
+     * Print warnings on a Connection to a specified PrintWriter.
+     *
+     * @param conn Connection to print warnings from
+     * @param pw PrintWriter to print to
+     */
+    public static void printWarnings(final Connection conn, final PrintWriter pw) {
+        if (conn != null) {
+            try {
+                printStackTrace(conn.getWarnings(), pw);
+            } catch (final SQLException e) {
+                printStackTrace(e, pw);
+            }
+        }
+    }
+
+    /**
+     * Rollback any changes made on the given connection.
+     * @param conn Connection to rollback.  A null value is legal.
+     * @throws SQLException if a database access error occurs
+     */
+    public static void rollback(final Connection conn) throws SQLException {
+        if (conn != null) {
+            conn.rollback();
+        }
+    }
+
+    /**
+     * Performs a rollback on the {@code Connection} then closes it,
+     * avoid closing if null.
+     *
+     * @param conn Connection to rollback.  A null value is legal.
+     * @throws SQLException if a database access error occurs
+     * @since 1.1
+     */
+    public static void rollbackAndClose(final Connection conn) throws SQLException {
+        if (conn != null) {
+            try {
+                conn.rollback();
+            } finally {
+                conn.close();
+            }
+        }
+    }
+
+    /**
+     * Performs a rollback on the {@code Connection} then closes it,
+     * avoid closing if null and hide any SQLExceptions that occur.
+     *
+     * @param conn Connection to rollback.  A null value is legal.
+     * @since 1.1
+     */
+    public static void rollbackAndCloseQuietly(final Connection conn) {
+        try {
+            rollbackAndClose(conn);
+        } catch (final SQLException e) { // NOPMD
+            // quiet
+        }
+    }
+
+    /**
+     * Performs a rollback on the {@code Connection}, avoid
+     * closing if null and hide any SQLExceptions that occur.
+     *
+     * @param conn Connection to rollback.  A null value is legal.
+     * @since DbUtils 2.0
+     */
+    public static void rollbackQuietly(final Connection conn) {
+        try {
+            rollback(conn);
+        } catch (final SQLException e) { // NOPMD
+            // quiet
+        }
+    }
+
+    /**
+     * Default constructor.
+     *
+     * Utility classes should not have a public or default constructor,
+     * but this one preserves retro-compatibility.
+     *
+     * @since 1.4
+     */
+    public DbUtils() {
+        // do nothing
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/dbutils/GenerousBeanProcessor.java b/src/main/java/org/apache/commons/dbutils/GenerousBeanProcessor.java
index cf9e5a6..877536d 100644
--- a/src/main/java/org/apache/commons/dbutils/GenerousBeanProcessor.java
+++ b/src/main/java/org/apache/commons/dbutils/GenerousBeanProcessor.java
@@ -1,72 +1,72 @@
-/*

- * 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.dbutils;

-

-import java.beans.PropertyDescriptor;

-import java.sql.ResultSetMetaData;

-import java.sql.SQLException;

-import java.util.Arrays;

-

-/**

- * Provides generous name matching (e.g. underscore-aware) from DB

- * columns to Java Bean properties.

- *

- * @since 1.6

- */

-public class GenerousBeanProcessor extends BeanProcessor {

-

-    /**

-     * Default constructor.

-     */

-    public GenerousBeanProcessor() {

-    }

-

-    @Override

-    protected int[] mapColumnsToProperties(final ResultSetMetaData rsmd,

-            final PropertyDescriptor[] props) throws SQLException {

-

-        final int cols = rsmd.getColumnCount();

-        final int[] columnToProperty = new int[cols + 1];

-        Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);

-

-        for (int col = 1; col <= cols; col++) {

-            String columnName = rsmd.getColumnLabel(col);

-

-            if (null == columnName || 0 == columnName.length()) {

-                columnName = rsmd.getColumnName(col);

-            }

-

-            final String generousColumnName = columnName

-                    .replace("_", "")   // more idiomatic to Java

-                    .replace(" ", "");  // can't have spaces in property names

-

-            for (int i = 0; i < props.length; i++) {

-                final String propName = props[i].getName();

-

-                // see if either the column name, or the generous one matches

-                if (columnName.equalsIgnoreCase(propName) ||

-                        generousColumnName.equalsIgnoreCase(propName)) {

-                    columnToProperty[col] = i;

-                    break;

-                }

-            }

-        }

-

-        return columnToProperty;

-    }

-

-}

+/*
+ * 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.dbutils;
+
+import java.beans.PropertyDescriptor;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.Arrays;
+
+/**
+ * Provides generous name matching (e.g. underscore-aware) from DB
+ * columns to Java Bean properties.
+ *
+ * @since 1.6
+ */
+public class GenerousBeanProcessor extends BeanProcessor {
+
+    /**
+     * Default constructor.
+     */
+    public GenerousBeanProcessor() {
+    }
+
+    @Override
+    protected int[] mapColumnsToProperties(final ResultSetMetaData rsmd,
+            final PropertyDescriptor[] props) throws SQLException {
+
+        final int cols = rsmd.getColumnCount();
+        final int[] columnToProperty = new int[cols + 1];
+        Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);
+
+        for (int col = 1; col <= cols; col++) {
+            String columnName = rsmd.getColumnLabel(col);
+
+            if (null == columnName || 0 == columnName.length()) {
+                columnName = rsmd.getColumnName(col);
+            }
+
+            final String generousColumnName = columnName
+                    .replace("_", "")   // more idiomatic to Java
+                    .replace(" ", "");  // can't have spaces in property names
+
+            for (int i = 0; i < props.length; i++) {
+                final String propName = props[i].getName();
+
+                // see if either the column name, or the generous one matches
+                if (columnName.equalsIgnoreCase(propName) ||
+                        generousColumnName.equalsIgnoreCase(propName)) {
+                    columnToProperty[col] = i;
+                    break;
+                }
+            }
+        }
+
+        return columnToProperty;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/dbutils/QueryLoader.java b/src/main/java/org/apache/commons/dbutils/QueryLoader.java
index 6fd7da1..9b813c8 100644
--- a/src/main/java/org/apache/commons/dbutils/QueryLoader.java
+++ b/src/main/java/org/apache/commons/dbutils/QueryLoader.java
@@ -1,141 +1,141 @@
-/*

- * 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.dbutils;

-

-import java.io.IOException;

-import java.io.InputStream;

-import java.util.HashMap;

-import java.util.Map;

-import java.util.Properties;

-import java.util.regex.Pattern;

-

-/**

- * {@code QueryLoader} is a registry for sets of queries so

- * that multiple copies of the same queries aren't loaded into memory.

- * This implementation loads properties files filled with query name to

- * SQL mappings.  This class is thread safe.

- */

-public class QueryLoader {

-

-    /**

-     * The Singleton INSTANCE of this class.

-     */

-    private static final QueryLoader INSTANCE = new QueryLoader();

-

-    /**

-     * Matches .xml file extensions.

-     */

-    private static final Pattern dotXml = Pattern.compile(".+\\.[xX][mM][lL]");

-

-    /**

-     * Return an INSTANCE of this class.

-     * @return The Singleton INSTANCE.

-     */

-    public static QueryLoader instance() {

-        return INSTANCE;

-    }

-

-    /**

-     * Maps query set names to Maps of their queries.

-     */

-    private final Map<String, Map<String, String>> queries = new HashMap<>();

-

-    /**

-     * QueryLoader constructor.

-     */

-    protected QueryLoader() {

-    }

-

-    /**

-     * Loads a Map of query names to SQL values.  The Maps are cached so a

-     * subsequent request to load queries from the same path will return

-     * the cached Map.  The properties file to load can be in either

-     * line-oriented or XML format.  XML formatted properties files must use a

-     * {@code .xml} file extension.

-     *

-     * @param path The path that the ClassLoader will use to find the file.

-     * This is <strong>not</strong> a file system path.  If you had a jarred

-     * Queries.properties file in the com.yourcorp.app.jdbc package you would

-     * pass "/com/yourcorp/app/jdbc/Queries.properties" to this method.

-     * @throws IOException if a file access error occurs

-     * @throws IllegalArgumentException if the ClassLoader can't find a file at

-     * the given path.

-     * @throws java.util.InvalidPropertiesFormatException if the XML properties file is

-     * invalid

-     * @return Map of query names to SQL values

-     * @see java.util.Properties

-     */

-    public synchronized Map<String, String> load(final String path) throws IOException {

-

-        Map<String, String> queryMap = this.queries.get(path);

-

-        if (queryMap == null) {

-            queryMap = loadQueries(path);

-            this.queries.put(path, queryMap);

-        }

-

-        return queryMap;

-    }

-

-    /**

-     * Loads a set of named queries into a Map object.  This implementation

-     * reads a properties file at the given path.  The properties file can be

-     * in either line-oriented or XML format.  XML formatted properties files

-     * must use a {@code .xml} file extension.

-

-     * @param path The path that the ClassLoader will use to find the file.

-     * @throws IOException if a file access error occurs

-     * @throws IllegalArgumentException if the ClassLoader can't find a file at

-     * the given path.

-     * @throws java.util.InvalidPropertiesFormatException if the XML properties file is

-     * invalid

-     * @since 1.1

-     * @return Map of query names to SQL values

-     * @see java.util.Properties

-     */

-    protected Map<String, String> loadQueries(final String path) throws IOException {

-        // Findbugs flags getClass().getResource as a bad practice; maybe we should change the API?

-        final Properties props;

-        try (InputStream in = getClass().getResourceAsStream(path)) {

-

-            if (in == null) {

-                throw new IllegalArgumentException(path + " not found.");

-            }

-            props = new Properties();

-            if (dotXml.matcher(path).matches()) {

-                props.loadFromXML(in);

-            } else {

-                props.load(in);

-            }

-        }

-

-        // Copy to HashMap for better performance

-

-        @SuppressWarnings({"rawtypes", "unchecked" }) // load() always creates <String,String> entries

-        final HashMap<String, String> hashMap = new HashMap(props);

-        return hashMap;

-    }

-

-    /**

-     * Removes the queries for the given path from the cache.

-     * @param path The path that the queries were loaded from.

-     */

-    public synchronized void unload(final String path) {

-        this.queries.remove(path);

-    }

-

-}

+/*
+ * 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.dbutils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.regex.Pattern;
+
+/**
+ * {@code QueryLoader} is a registry for sets of queries so
+ * that multiple copies of the same queries aren't loaded into memory.
+ * This implementation loads properties files filled with query name to
+ * SQL mappings.  This class is thread safe.
+ */
+public class QueryLoader {
+
+    /**
+     * The Singleton INSTANCE of this class.
+     */
+    private static final QueryLoader INSTANCE = new QueryLoader();
+
+    /**
+     * Matches .xml file extensions.
+     */
+    private static final Pattern dotXml = Pattern.compile(".+\\.[xX][mM][lL]");
+
+    /**
+     * Return an INSTANCE of this class.
+     * @return The Singleton INSTANCE.
+     */
+    public static QueryLoader instance() {
+        return INSTANCE;
+    }
+
+    /**
+     * Maps query set names to Maps of their queries.
+     */
+    private final Map<String, Map<String, String>> queries = new HashMap<>();
+
+    /**
+     * QueryLoader constructor.
+     */
+    protected QueryLoader() {
+    }
+
+    /**
+     * Loads a Map of query names to SQL values.  The Maps are cached so a
+     * subsequent request to load queries from the same path will return
+     * the cached Map.  The properties file to load can be in either
+     * line-oriented or XML format.  XML formatted properties files must use a
+     * {@code .xml} file extension.
+     *
+     * @param path The path that the ClassLoader will use to find the file.
+     * This is <strong>not</strong> a file system path.  If you had a jarred
+     * Queries.properties file in the com.yourcorp.app.jdbc package you would
+     * pass "/com/yourcorp/app/jdbc/Queries.properties" to this method.
+     * @throws IOException if a file access error occurs
+     * @throws IllegalArgumentException if the ClassLoader can't find a file at
+     * the given path.
+     * @throws java.util.InvalidPropertiesFormatException if the XML properties file is
+     * invalid
+     * @return Map of query names to SQL values
+     * @see java.util.Properties
+     */
+    public synchronized Map<String, String> load(final String path) throws IOException {
+
+        Map<String, String> queryMap = this.queries.get(path);
+
+        if (queryMap == null) {
+            queryMap = loadQueries(path);
+            this.queries.put(path, queryMap);
+        }
+
+        return queryMap;
+    }
+
+    /**
+     * Loads a set of named queries into a Map object.  This implementation
+     * reads a properties file at the given path.  The properties file can be
+     * in either line-oriented or XML format.  XML formatted properties files
+     * must use a {@code .xml} file extension.
+
+     * @param path The path that the ClassLoader will use to find the file.
+     * @throws IOException if a file access error occurs
+     * @throws IllegalArgumentException if the ClassLoader can't find a file at
+     * the given path.
+     * @throws java.util.InvalidPropertiesFormatException if the XML properties file is
+     * invalid
+     * @since 1.1
+     * @return Map of query names to SQL values
+     * @see java.util.Properties
+     */
+    protected Map<String, String> loadQueries(final String path) throws IOException {
+        // Findbugs flags getClass().getResource as a bad practice; maybe we should change the API?
+        final Properties props;
+        try (InputStream in = getClass().getResourceAsStream(path)) {
+
+            if (in == null) {
+                throw new IllegalArgumentException(path + " not found.");
+            }
+            props = new Properties();
+            if (dotXml.matcher(path).matches()) {
+                props.loadFromXML(in);
+            } else {
+                props.load(in);
+            }
+        }
+
+        // Copy to HashMap for better performance
+
+        @SuppressWarnings({"rawtypes", "unchecked" }) // load() always creates <String,String> entries
+        final HashMap<String, String> hashMap = new HashMap(props);
+        return hashMap;
+    }
+
+    /**
+     * Removes the queries for the given path from the cache.
+     * @param path The path that the queries were loaded from.
+     */
+    public synchronized void unload(final String path) {
+        this.queries.remove(path);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/dbutils/QueryRunner.java b/src/main/java/org/apache/commons/dbutils/QueryRunner.java
index 688cecb..bb86af8 100644
--- a/src/main/java/org/apache/commons/dbutils/QueryRunner.java
+++ b/src/main/java/org/apache/commons/dbutils/QueryRunner.java
@@ -1,845 +1,845 @@
-/*

- * 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.dbutils;

-

-import java.sql.CallableStatement;

-import java.sql.Connection;

-import java.sql.ParameterMetaData;

-import java.sql.PreparedStatement;

-import java.sql.ResultSet;

-import java.sql.SQLException;

-import java.sql.Statement;

-import java.util.LinkedList;

-import java.util.List;

-

-import javax.sql.DataSource;

-

-/**

- * Executes SQL queries with pluggable strategies for handling

- * {@code ResultSet}s.  This class is thread safe.

- *

- * @see ResultSetHandler

- */

-public class QueryRunner extends AbstractQueryRunner {

-

-    /**

-     * Constructor for QueryRunner.

-     */

-    public QueryRunner() {

-    }

-

-    /**

-     * Constructor for QueryRunner that controls the use of {@code ParameterMetaData}.

-     *

-     * @param pmdKnownBroken Some drivers don't support {@link java.sql.ParameterMetaData#getParameterType(int) };

-     * if {@code pmdKnownBroken} is set to true, we won't even try it; if false, we'll try it,

-     * and if it breaks, we'll remember not to use it again.

-     */

-    public QueryRunner(final boolean pmdKnownBroken) {

-        super(pmdKnownBroken);

-    }

-

-    /**

-     * Constructor for QueryRunner that takes a {@code DataSource} to use.

-     *

-     * Methods that do not take a {@code Connection} parameter will retrieve connections from this

-     * {@code DataSource}.

-     *

-     * @param ds The {@code DataSource} to retrieve connections from.

-     */

-    public QueryRunner(final DataSource ds) {

-        super(ds);

-    }

-

-    /**

-     * Constructor for QueryRunner that takes a {@code DataSource} and controls the use of {@code ParameterMetaData}.

-     * Methods that do not take a {@code Connection} parameter will retrieve connections from this

-     * {@code DataSource}.

-     *

-     * @param ds The {@code DataSource} to retrieve connections from.

-     * @param pmdKnownBroken Some drivers don't support {@link java.sql.ParameterMetaData#getParameterType(int) };

-     * if {@code pmdKnownBroken} is set to true, we won't even try it; if false, we'll try it,

-     * and if it breaks, we'll remember not to use it again.

-     */

-    public QueryRunner(final DataSource ds, final boolean pmdKnownBroken) {

-        super(ds, pmdKnownBroken);

-    }

-

-    /**

-     * Constructor for QueryRunner that takes a {@code DataSource}, a {@code StatementConfiguration}, and

-     * controls the use of {@code ParameterMetaData}.  Methods that do not take a {@code Connection} parameter

-     * will retrieve connections from this {@code DataSource}.

-     *

-     * @param ds The {@code DataSource} to retrieve connections from.

-     * @param pmdKnownBroken Some drivers don't support {@link java.sql.ParameterMetaData#getParameterType(int) };

-     * if {@code pmdKnownBroken} is set to true, we won't even try it; if false, we'll try it,

-     * and if it breaks, we'll remember not to use it again.

-     * @param stmtConfig The configuration to apply to statements when they are prepared.

-     */

-    public QueryRunner(final DataSource ds, final boolean pmdKnownBroken, final StatementConfiguration stmtConfig) {

-        super(ds, pmdKnownBroken, stmtConfig);

-    }

-

-    /**

-     * Constructor for QueryRunner that takes a {@code DataSource} to use and a {@code StatementConfiguration}.

-     *

-     * Methods that do not take a {@code Connection} parameter will retrieve connections from this

-     * {@code DataSource}.

-     *

-     * @param ds The {@code DataSource} to retrieve connections from.

-     * @param stmtConfig The configuration to apply to statements when they are prepared.

-     */

-    public QueryRunner(final DataSource ds, final StatementConfiguration stmtConfig) {

-        super(ds, stmtConfig);

-    }

-

-    /**

-     * Constructor for QueryRunner that takes a {@code StatementConfiguration} to configure statements when

-     * preparing them.

-     *

-     * @param stmtConfig The configuration to apply to statements when they are prepared.

-     */

-    public QueryRunner(final StatementConfiguration stmtConfig) {

-        super(stmtConfig);

-    }

-

-    /**

-     * Execute a batch of SQL INSERT, UPDATE, or DELETE queries.

-     *

-     * @param conn The Connection to use to run the query.  The caller is

-     * responsible for closing this Connection.

-     * @param sql The SQL to execute.

-     * @param params An array of query replacement parameters.  Each row in

-     * this array is one set of batch replacement values.

-     * @return The number of rows updated per statement.

-     * @throws SQLException if a database access error occurs

-     * @since 1.1

-     */

-    public int[] batch(final Connection conn, final String sql, final Object[][] params) throws SQLException {

-        if (conn == null) {

-            throw new SQLException("Null connection");

-        }

-

-        if (sql == null) {

-            throw new SQLException("Null SQL statement");

-        }

-

-        if (params == null) {

-            throw new SQLException("Null parameters. If parameters aren't need, pass an empty array.");

-        }

-

-        PreparedStatement stmt = null;

-        ParameterMetaData pmd = null;

-        int[] rows = null;

-        try {

-            stmt = this.prepareStatement(conn, sql);

-            // When the batch size is large, prefetching parameter metadata before filling

-            // the statement can reduce lots of JDBC communications.

-            pmd = getParameterMetaData(stmt);

-

-            for (final Object[] param : params) {

-                this.fillStatement(stmt, pmd, param);

-                stmt.addBatch();

-            }

-            rows = stmt.executeBatch();

-

-        } catch (final SQLException e) {

-            rethrow(e, sql, (Object[])params);

-        } finally {

-            close(stmt);

-        }

-

-        return rows;

-    }

-

-    /**

-     * Execute a batch of SQL INSERT, UPDATE, or DELETE queries.  The

-     * {@code Connection} is retrieved from the {@code DataSource}

-     * set in the constructor.  This {@code Connection} must be in

-     * auto-commit mode or the update will not be saved.

-     *

-     * @param sql The SQL to execute.

-     * @param params An array of query replacement parameters.  Each row in

-     * this array is one set of batch replacement values.

-     * @return The number of rows updated per statement.

-     * @throws SQLException if a database access error occurs

-     * @since 1.1

-     */

-    public int[] batch(final String sql, final Object[][] params) throws SQLException {

-        try (Connection conn = prepareConnection()) {

-            return this.batch(conn, sql, params);

-        }

-    }

-

-    /**

-     * Execute an SQL statement, including a stored procedure call, which does

-     * not return any result sets.

-     * Any parameters which are instances of {@link OutParameter} will be

-     * registered as OUT parameters.

-     * <p>

-     * Use this method when invoking a stored procedure with OUT parameters

-     * that does not return any result sets.  If you are not invoking a stored

-     * procedure, or the stored procedure has no OUT parameters, consider using

-     * {@link #update(java.sql.Connection, String, Object...) }.

-     * If the stored procedure returns result sets, use

-     * {@link #execute(java.sql.Connection, String, org.apache.commons.dbutils.ResultSetHandler, Object...) }.

-     *

-     * @param conn The connection to use to run the query.

-     * @param sql The SQL to execute.

-     * @param params The query replacement parameters.

-     * @return The number of rows updated.

-     * @throws SQLException if a database access error occurs

-     */

-    public int execute(final Connection conn, final String sql, final Object... params) throws SQLException {

-        if (conn == null) {

-            throw new SQLException("Null connection");

-        }

-

-        if (sql == null) {

-            throw new SQLException("Null SQL statement");

-        }

-

-        CallableStatement stmt = null;

-        int rows = 0;

-

-        try {

-            stmt = prepareCall(conn, sql);

-            this.fillStatement(stmt, params);

-            stmt.execute();

-            rows = stmt.getUpdateCount();

-            retrieveOutParameters(stmt, params);

-

-        } catch (final SQLException e) {

-            rethrow(e, sql, params);

-

-        } finally {

-            close(stmt);

-        }

-

-        return rows;

-    }

-

-    /**

-     * Execute an SQL statement, including a stored procedure call, which

-     * returns one or more result sets.

-     * Any parameters which are instances of {@link OutParameter} will be

-     * registered as OUT parameters.

-     * <p>

-     * Use this method when: a) running SQL statements that return multiple

-     * result sets; b) invoking a stored procedure that return result

-     * sets and OUT parameters.  Otherwise you may wish to use

-     * {@link #query(java.sql.Connection, String, org.apache.commons.dbutils.ResultSetHandler, Object...) }

-     * (if there are no OUT parameters) or

-     * {@link #execute(java.sql.Connection, String, Object...) }

-     * (if there are no result sets).

-     *

-     * @param <T> The type of object that the handler returns

-     * @param conn The connection to use to run the query.

-     * @param sql The SQL to execute.

-     * @param rsh The result set handler

-     * @param params The query replacement parameters.

-     * @return A list of objects generated by the handler

-     * @throws SQLException if a database access error occurs

-     */

-    public <T> List<T> execute(final Connection conn, final String sql, final ResultSetHandler<T> rsh, final Object... params) throws SQLException {

-        if (conn == null) {

-            throw new SQLException("Null connection");

-        }

-

-        if (sql == null) {

-            throw new SQLException("Null SQL statement");

-        }

-

-        if (rsh == null) {

-            throw new SQLException("Null ResultSetHandler");

-        }

-

-        CallableStatement stmt = null;

-        final List<T> results = new LinkedList<>();

-

-        try {

-            stmt = prepareCall(conn, sql);

-            this.fillStatement(stmt, params);

-            boolean moreResultSets = stmt.execute();

-            // Handle multiple result sets by passing them through the handler

-            // retaining the final result

-            while (moreResultSets) {

-                try (@SuppressWarnings("resource")

-                // assume the ResultSet wrapper properly closes

-                ResultSet resultSet = wrap(stmt.getResultSet())) {

-                    results.add(rsh.handle(resultSet));

-                    moreResultSets = stmt.getMoreResults();

-                }

-            }

-            retrieveOutParameters(stmt, params);

-

-        } catch (final SQLException e) {

-            rethrow(e, sql, params);

-

-        } finally {

-            close(stmt);

-        }

-

-        return results;

-    }

-

-    /**

-     * Execute an SQL statement, including a stored procedure call, which does

-     * not return any result sets.

-     * Any parameters which are instances of {@link OutParameter} will be

-     * registered as OUT parameters.

-     * <p>

-     * Use this method when invoking a stored procedure with OUT parameters

-     * that does not return any result sets.  If you are not invoking a stored

-     * procedure, or the stored procedure has no OUT parameters, consider using

-     * {@link #update(String, Object...) }.

-     * If the stored procedure returns result sets, use

-     * {@link #execute(String, org.apache.commons.dbutils.ResultSetHandler, Object...) }.

-     * <p>

-     * The {@code Connection} is retrieved from the {@code DataSource}

-     * set in the constructor.  This {@code Connection} must be in

-     * auto-commit mode or the update will not be saved.

-     *

-     * @param sql The SQL statement to execute.

-     * @param params Initializes the CallableStatement's parameters (i.e. '?').

-     * @throws SQLException if a database access error occurs

-     * @return The number of rows updated.

-     */

-    public int execute(final String sql, final Object... params) throws SQLException {

-        try (Connection conn = prepareConnection()) {

-            return this.execute(conn, sql, params);

-        }

-    }

-

-    /**

-     * Execute an SQL statement, including a stored procedure call, which

-     * returns one or more result sets.

-     * Any parameters which are instances of {@link OutParameter} will be

-     * registered as OUT parameters.

-     * <p>

-     * Use this method when: a) running SQL statements that return multiple

-     * result sets; b) invoking a stored procedure that return result

-     * sets and OUT parameters.  Otherwise you may wish to use

-     * {@link #query(String, org.apache.commons.dbutils.ResultSetHandler, Object...) }

-     * (if there are no OUT parameters) or

-     * {@link #execute(String, Object...) }

-     * (if there are no result sets).

-     *

-     * @param <T> The type of object that the handler returns

-     * @param sql The SQL to execute.

-     * @param rsh The result set handler

-     * @param params The query replacement parameters.

-     * @return A list of objects generated by the handler

-     * @throws SQLException if a database access error occurs

-     */

-    public <T> List<T> execute(final String sql, final ResultSetHandler<T> rsh, final Object... params) throws SQLException {

-        try (Connection conn = prepareConnection()) {

-            return this.execute(conn, sql, rsh, params);

-        }

-    }

-

-    /**

-     * Execute an SQL INSERT query without replacement parameters.

-     * @param <T> The type of object that the handler returns

-     * @param conn The connection to use to run the query.

-     * @param sql The SQL to execute.

-     * @param rsh The handler used to create the result object from

-     * the {@code ResultSet} of auto-generated keys.

-     * @return An object generated by the handler.

-     * @throws SQLException if a database access error occurs

-     * @since 1.6

-     */

-    public <T> T insert(final Connection conn, final String sql, final ResultSetHandler<T> rsh) throws SQLException {

-        return insert(conn, sql, rsh, (Object[]) null);

-    }

-

-    /**

-     * Execute an SQL INSERT query.

-     * @param <T> The type of object that the handler returns

-     * @param conn The connection to use to run the query.

-     * @param sql The SQL to execute.

-     * @param rsh The handler used to create the result object from

-     * the {@code ResultSet} of auto-generated keys.

-     * @param params The query replacement parameters.

-     * @return An object generated by the handler.

-     * @throws SQLException if a database access error occurs

-     * @since 1.6

-     */

-    public <T> T insert(final Connection conn, final String sql, final ResultSetHandler<T> rsh, final Object... params) throws SQLException {

-        if (conn == null) {

-            throw new SQLException("Null connection");

-        }

-

-        if (sql == null) {

-            throw new SQLException("Null SQL statement");

-        }

-

-        if (rsh == null) {

-            throw new SQLException("Null ResultSetHandler");

-        }

-

-        Statement stmt = null;

-        T generatedKeys = null;

-

-        try {

-            if (params != null && params.length > 0) {

-                final PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

-                stmt = ps;

-                this.fillStatement(ps, params);

-                ps.executeUpdate();

-            } else {

-                stmt = conn.createStatement();

-                stmt.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);

-            }

-            try (ResultSet resultSet = stmt.getGeneratedKeys()) {

-                generatedKeys = rsh.handle(resultSet);

-            }

-        } catch (final SQLException e) {

-            rethrow(e, sql, params);

-        } finally {

-            close(stmt);

-        }

-

-        return generatedKeys;

-    }

-

-    /**

-     * Executes the given INSERT SQL without any replacement parameters.

-     * The {@code Connection} is retrieved from the

-     * {@code DataSource} set in the constructor.

-     * @param <T> The type of object that the handler returns

-     * @param sql The SQL statement to execute.

-     * @param rsh The handler used to create the result object from

-     * the {@code ResultSet} of auto-generated keys.

-     * @return An object generated by the handler.

-     * @throws SQLException if a database access error occurs

-     * @since 1.6

-     */

-    public <T> T insert(final String sql, final ResultSetHandler<T> rsh) throws SQLException {

-        try (Connection conn = prepareConnection()) {

-            return insert(conn, sql, rsh, (Object[]) null);

-        }

-    }

-

-    /**

-     * Executes the given INSERT SQL statement. The

-     * {@code Connection} is retrieved from the {@code DataSource}

-     * set in the constructor.  This {@code Connection} must be in

-     * auto-commit mode or the insert will not be saved.

-     * @param <T> The type of object that the handler returns

-     * @param sql The SQL statement to execute.

-     * @param rsh The handler used to create the result object from

-     * the {@code ResultSet} of auto-generated keys.

-     * @param params Initializes the PreparedStatement's IN (i.e. '?')

-     * @return An object generated by the handler.

-     * @throws SQLException if a database access error occurs

-     * @since 1.6

-     */

-    public <T> T insert(final String sql, final ResultSetHandler<T> rsh, final Object... params) throws SQLException {

-        try (Connection conn = prepareConnection()) {

-            return insert(conn, sql, rsh, params);

-        }

-    }

-

-    /**

-     * Executes the given batch of INSERT SQL statements.

-     * @param <T> The type of object that the handler returns

-     * @param conn The connection to use to run the query.

-     * @param sql The SQL to execute.

-     * @param rsh The handler used to create the result object from

-     * the {@code ResultSet} of auto-generated keys.

-     * @param params The query replacement parameters.

-     * @return The result generated by the handler.

-     * @throws SQLException if a database access error occurs

-     * @since 1.6

-     */

-    public <T> T insertBatch(final Connection conn, final String sql, final ResultSetHandler<T> rsh, final Object[][] params) throws SQLException {

-        if (conn == null) {

-            throw new SQLException("Null connection");

-        }

-

-        if (sql == null) {

-            throw new SQLException("Null SQL statement");

-        }

-

-        if (params == null) {

-            throw new SQLException("Null parameters. If parameters aren't need, pass an empty array.");

-        }

-

-        PreparedStatement stmt = null;

-        T generatedKeys = null;

-        try {

-            stmt = this.prepareStatement(conn, sql, Statement.RETURN_GENERATED_KEYS);

-

-            for (final Object[] param : params) {

-                this.fillStatement(stmt, param);

-                stmt.addBatch();

-            }

-            stmt.executeBatch();

-            try (ResultSet resultSet = stmt.getGeneratedKeys()) {

-                generatedKeys = rsh.handle(resultSet);

-            }

-        } catch (final SQLException e) {

-            rethrow(e, sql, (Object[])params);

-        } finally {

-            close(stmt);

-        }

-

-        return generatedKeys;

-    }

-

-    /**

-     * Executes the given batch of INSERT SQL statements. The

-     * {@code Connection} is retrieved from the {@code DataSource}

-     * set in the constructor.  This {@code Connection} must be in

-     * auto-commit mode or the insert will not be saved.

-     * @param <T> The type of object that the handler returns

-     * @param sql The SQL statement to execute.

-     * @param rsh The handler used to create the result object from

-     * the {@code ResultSet} of auto-generated keys.

-     * @param params Initializes the PreparedStatement's IN (i.e. '?')

-     * @return The result generated by the handler.

-     * @throws SQLException if a database access error occurs

-     * @since 1.6

-     */

-    public <T> T insertBatch(final String sql, final ResultSetHandler<T> rsh, final Object[][] params) throws SQLException {

-        try (Connection conn = prepareConnection()) {

-            return insertBatch(conn, sql, rsh, params);

-        }

-    }

-

-    /**

-     * Execute an SQL SELECT query with a single replacement parameter. The

-     * caller is responsible for closing the connection.

-     * @param <T> The type of object that the handler returns

-     * @param conn The connection to execute the query in.

-     * @param sql The query to execute.

-     * @param param The replacement parameter.

-     * @param rsh The handler that converts the results into an object.

-     * @return The object returned by the handler.

-     * @throws SQLException if a database access error occurs

-     * @deprecated Use {@link #query(Connection, String, ResultSetHandler, Object...)}

-     */

-    @Deprecated

-    public <T> T query(final Connection conn, final String sql, final Object param, final ResultSetHandler<T> rsh) throws SQLException {

-        return this.<T>query(conn, sql, rsh, param);

-    }

-

-    /**

-     * Execute an SQL SELECT query with replacement parameters.  The

-     * caller is responsible for closing the connection.

-     * @param <T> The type of object that the handler returns

-     * @param conn The connection to execute the query in.

-     * @param sql The query to execute.

-     * @param params The replacement parameters.

-     * @param rsh The handler that converts the results into an object.

-     * @return The object returned by the handler.

-     * @throws SQLException if a database access error occurs

-     * @deprecated Use {@link #query(Connection,String,ResultSetHandler,Object...)} instead

-     */

-    @Deprecated

-    public <T> T query(final Connection conn, final String sql, final Object[] params, final ResultSetHandler<T> rsh) throws SQLException {

-        return this.<T>query(conn, sql, rsh, params);

-    }

-

-    /**

-     * Execute an SQL SELECT query without any replacement parameters.  The

-     * caller is responsible for closing the connection.

-     * @param <T> The type of object that the handler returns

-     * @param conn The connection to execute the query in.

-     * @param sql The query to execute.

-     * @param rsh The handler that converts the results into an object.

-     * @return The object returned by the handler.

-     * @throws SQLException if a database access error occurs

-     */

-    public <T> T query(final Connection conn, final String sql, final ResultSetHandler<T> rsh) throws SQLException {

-        return this.<T>query(conn, sql, rsh, (Object[]) null);

-    }

-

-    /**

-     * Execute an SQL SELECT query with replacement parameters.  The

-     * caller is responsible for closing the connection.

-     * @param <T> The type of object that the handler returns

-     * @param conn The connection to execute the query in.

-     * @param sql The query to execute.

-     * @param rsh The handler that converts the results into an object.

-     * @param params The replacement parameters.

-     * @return The object returned by the handler.

-     * @throws SQLException if a database access error occurs

-     */

-    public <T> T query(final Connection conn, final String sql, final ResultSetHandler<T> rsh, final Object... params) throws SQLException {

-        if (conn == null) {

-            throw new SQLException("Null connection");

-        }

-

-        if (sql == null) {

-            throw new SQLException("Null SQL statement");

-        }

-

-        if (rsh == null) {

-            throw new SQLException("Null ResultSetHandler");

-        }

-

-        Statement stmt = null;

-        ResultSet resultSet = null;

-        T result = null;

-

-        try {

-            if (params != null && params.length > 0) {

-                final PreparedStatement ps = this.prepareStatement(conn, sql);

-                stmt = ps;

-                this.fillStatement(ps, params);

-                resultSet = wrap(ps.executeQuery());

-            } else {

-                stmt = conn.createStatement();

-                resultSet = wrap(stmt.executeQuery(sql));

-            }

-            result = rsh.handle(resultSet);

-

-        } catch (final SQLException e) {

-            rethrow(e, sql, params);

-

-        } finally {

-            closeQuietly(resultSet);

-            closeQuietly(stmt);

-        }

-

-        return result;

-    }

-

-    /**

-     * Executes the given SELECT SQL with a single replacement parameter.

-     * The {@code Connection} is retrieved from the

-     * {@code DataSource} set in the constructor.

-     * @param <T> The type of object that the handler returns

-     * @param sql The SQL statement to execute.

-     * @param param The replacement parameter.

-     * @param rsh The handler used to create the result object from

-     * the {@code ResultSet}.

-     *

-     * @return An object generated by the handler.

-     * @throws SQLException if a database access error occurs

-     * @deprecated Use {@link #query(String, ResultSetHandler, Object...)}

-     */

-    @Deprecated

-    public <T> T query(final String sql, final Object param, final ResultSetHandler<T> rsh) throws SQLException {

-        try (Connection conn = prepareConnection()) {

-            return this.<T>query(conn, sql, rsh, param);

-        }

-    }

-

-    /**

-     * Executes the given SELECT SQL query and returns a result object.

-     * The {@code Connection} is retrieved from the

-     * {@code DataSource} set in the constructor.

-     * @param <T> The type of object that the handler returns

-     * @param sql The SQL statement to execute.

-     * @param params Initialize the PreparedStatement's IN parameters with

-     * this array.

-     *

-     * @param rsh The handler used to create the result object from

-     * the {@code ResultSet}.

-     *

-     * @return An object generated by the handler.

-     * @throws SQLException if a database access error occurs

-     * @deprecated Use {@link #query(String, ResultSetHandler, Object...)}

-     */

-    @Deprecated

-    public <T> T query(final String sql, final Object[] params, final ResultSetHandler<T> rsh) throws SQLException {

-        try (Connection conn = prepareConnection()) {

-            return this.<T>query(conn, sql, rsh, params);

-        }

-    }

-

-    /**

-     * Executes the given SELECT SQL without any replacement parameters.

-     * The {@code Connection} is retrieved from the

-     * {@code DataSource} set in the constructor.

-     * @param <T> The type of object that the handler returns

-     * @param sql The SQL statement to execute.

-     * @param rsh The handler used to create the result object from

-     * the {@code ResultSet}.

-     *

-     * @return An object generated by the handler.

-     * @throws SQLException if a database access error occurs

-     */

-    public <T> T query(final String sql, final ResultSetHandler<T> rsh) throws SQLException {

-        try (Connection conn = prepareConnection()) {

-            return this.<T>query(conn, sql, rsh, (Object[]) null);

-        }

-    }

-

-    /**

-     * Executes the given SELECT SQL query and returns a result object.

-     * The {@code Connection} is retrieved from the

-     * {@code DataSource} set in the constructor.

-     * @param <T> The type of object that the handler returns

-     * @param sql The SQL statement to execute.

-     * @param rsh The handler used to create the result object from

-     * the {@code ResultSet}.

-     * @param params Initialize the PreparedStatement's IN parameters with

-     * this array.

-     * @return An object generated by the handler.

-     * @throws SQLException if a database access error occurs

-     */

-    public <T> T query(final String sql, final ResultSetHandler<T> rsh, final Object... params) throws SQLException {

-        try (Connection conn = prepareConnection()) {

-            return this.<T>query(conn, sql, rsh, params);

-        }

-    }

-

-    /**

-     * Set the value on all the {@link OutParameter} instances in the

-     * {@code params} array using the OUT parameter values from the

-     * {@code stmt}.

-     * @param stmt the statement from which to retrieve OUT parameter values

-     * @param params the parameter array for the statement invocation

-     * @throws SQLException when the value could not be retrieved from the

-     * statement.

-     */

-    private void retrieveOutParameters(final CallableStatement stmt, final Object[] params) throws SQLException {

-        if (params != null) {

-            for (int i = 0; i < params.length; i++) {

-                if (params[i] instanceof OutParameter) {

-                    ((OutParameter<?>) params[i]).setValue(stmt, i + 1);

-                }

-            }

-        }

-    }

-

-    /**

-     * Execute an SQL INSERT, UPDATE, or DELETE query without replacement

-     * parameters.

-     *

-     * @param conn The connection to use to run the query.

-     * @param sql The SQL to execute.

-     * @return The number of rows updated.

-     * @throws SQLException if a database access error occurs

-     */

-    public int update(final Connection conn, final String sql) throws SQLException {

-        return this.update(conn, sql, (Object[]) null);

-    }

-

-    /**

-     * Execute an SQL INSERT, UPDATE, or DELETE query with a single replacement

-     * parameter.

-     *

-     * @param conn The connection to use to run the query.

-     * @param sql The SQL to execute.

-     * @param param The replacement parameter.

-     * @return The number of rows updated.

-     * @throws SQLException if a database access error occurs

-     */

-    public int update(final Connection conn, final String sql, final Object param) throws SQLException {

-        return this.update(conn, sql, new Object[] { param });

-    }

-

-    /**

-     * Execute an SQL INSERT, UPDATE, or DELETE query.

-     *

-     * @param conn The connection to use to run the query.

-     * @param sql The SQL to execute.

-     * @param params The query replacement parameters.

-     * @return The number of rows updated.

-     * @throws SQLException if a database access error occurs

-     */

-    public int update(final Connection conn, final String sql, final Object... params) throws SQLException {

-        if (conn == null) {

-            throw new SQLException("Null connection");

-        }

-

-        if (sql == null) {

-            throw new SQLException("Null SQL statement");

-        }

-

-        Statement stmt = null;

-        int rows = 0;

-

-        try {

-            if (params != null && params.length > 0) {

-                final PreparedStatement ps = this.prepareStatement(conn, sql);

-                stmt = ps;

-                this.fillStatement(ps, params);

-                rows = ps.executeUpdate();

-            } else {

-                stmt = conn.createStatement();

-                rows = stmt.executeUpdate(sql);

-            }

-

-        } catch (final SQLException e) {

-            rethrow(e, sql, params);

-

-        } finally {

-            close(stmt);

-        }

-

-        return rows;

-    }

-

-    /**

-     * Executes the given INSERT, UPDATE, or DELETE SQL statement without

-     * any replacement parameters. The {@code Connection} is retrieved

-     * from the {@code DataSource} set in the constructor.  This

-     * {@code Connection} must be in auto-commit mode or the update will

-     * not be saved.

-     *

-     * @param sql The SQL statement to execute.

-     * @throws SQLException if a database access error occurs

-     * @return The number of rows updated.

-     */

-    public int update(final String sql) throws SQLException {

-        try (Connection conn = prepareConnection()) {

-            return this.update(conn, sql, (Object[]) null);

-        }

-    }

-

-    /**

-     * Executes the given INSERT, UPDATE, or DELETE SQL statement with

-     * a single replacement parameter.  The {@code Connection} is

-     * retrieved from the {@code DataSource} set in the constructor.

-     * This {@code Connection} must be in auto-commit mode or the

-     * update will not be saved.

-     *

-     * @param sql The SQL statement to execute.

-     * @param param The replacement parameter.

-     * @throws SQLException if a database access error occurs

-     * @return The number of rows updated.

-     */

-    public int update(final String sql, final Object param) throws SQLException {

-        try (Connection conn = prepareConnection()) {

-            return this.update(conn, sql, param);

-        }

-    }

-

-    /**

-     * Executes the given INSERT, UPDATE, or DELETE SQL statement.  The

-     * {@code Connection} is retrieved from the {@code DataSource}

-     * set in the constructor.  This {@code Connection} must be in

-     * auto-commit mode or the update will not be saved.

-     *

-     * @param sql The SQL statement to execute.

-     * @param params Initializes the PreparedStatement's IN (i.e. '?')

-     * parameters.

-     * @throws SQLException if a database access error occurs

-     * @return The number of rows updated.

-     */

-    public int update(final String sql, final Object... params) throws SQLException {

-        try (Connection conn = prepareConnection()) {

-            return this.update(conn, sql, params);

-        }

-    }

-}

+/*
+ * 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.dbutils;
+
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.ParameterMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+/**
+ * Executes SQL queries with pluggable strategies for handling
+ * {@code ResultSet}s.  This class is thread safe.
+ *
+ * @see ResultSetHandler
+ */
+public class QueryRunner extends AbstractQueryRunner {
+
+    /**
+     * Constructor for QueryRunner.
+     */
+    public QueryRunner() {
+    }
+
+    /**
+     * Constructor for QueryRunner that controls the use of {@code ParameterMetaData}.
+     *
+     * @param pmdKnownBroken Some drivers don't support {@link java.sql.ParameterMetaData#getParameterType(int) };
+     * if {@code pmdKnownBroken} is set to true, we won't even try it; if false, we'll try it,
+     * and if it breaks, we'll remember not to use it again.
+     */
+    public QueryRunner(final boolean pmdKnownBroken) {
+        super(pmdKnownBroken);
+    }
+
+    /**
+     * Constructor for QueryRunner that takes a {@code DataSource} to use.
+     *
+     * Methods that do not take a {@code Connection} parameter will retrieve connections from this
+     * {@code DataSource}.
+     *
+     * @param ds The {@code DataSource} to retrieve connections from.
+     */
+    public QueryRunner(final DataSource ds) {
+        super(ds);
+    }
+
+    /**
+     * Constructor for QueryRunner that takes a {@code DataSource} and controls the use of {@code ParameterMetaData}.
+     * Methods that do not take a {@code Connection} parameter will retrieve connections from this
+     * {@code DataSource}.
+     *
+     * @param ds The {@code DataSource} to retrieve connections from.
+     * @param pmdKnownBroken Some drivers don't support {@link java.sql.ParameterMetaData#getParameterType(int) };
+     * if {@code pmdKnownBroken} is set to true, we won't even try it; if false, we'll try it,
+     * and if it breaks, we'll remember not to use it again.
+     */
+    public QueryRunner(final DataSource ds, final boolean pmdKnownBroken) {
+        super(ds, pmdKnownBroken);
+    }
+
+    /**
+     * Constructor for QueryRunner that takes a {@code DataSource}, a {@code StatementConfiguration}, and
+     * controls the use of {@code ParameterMetaData}.  Methods that do not take a {@code Connection} parameter
+     * will retrieve connections from this {@code DataSource}.
+     *
+     * @param ds The {@code DataSource} to retrieve connections from.
+     * @param pmdKnownBroken Some drivers don't support {@link java.sql.ParameterMetaData#getParameterType(int) };
+     * if {@code pmdKnownBroken} is set to true, we won't even try it; if false, we'll try it,
+     * and if it breaks, we'll remember not to use it again.
+     * @param stmtConfig The configuration to apply to statements when they are prepared.
+     */
+    public QueryRunner(final DataSource ds, final boolean pmdKnownBroken, final StatementConfiguration stmtConfig) {
+        super(ds, pmdKnownBroken, stmtConfig);
+    }
+
+    /**
+     * Constructor for QueryRunner that takes a {@code DataSource} to use and a {@code StatementConfiguration}.
+     *
+     * Methods that do not take a {@code Connection} parameter will retrieve connections from this
+     * {@code DataSource}.
+     *
+     * @param ds The {@code DataSource} to retrieve connections from.
+     * @param stmtConfig The configuration to apply to statements when they are prepared.
+     */
+    public QueryRunner(final DataSource ds, final StatementConfiguration stmtConfig) {
+        super(ds, stmtConfig);
+    }
+
+    /**
+     * Constructor for QueryRunner that takes a {@code StatementConfiguration} to configure statements when
+     * preparing them.
+     *
+     * @param stmtConfig The configuration to apply to statements when they are prepared.
+     */
+    public QueryRunner(final StatementConfiguration stmtConfig) {
+        super(stmtConfig);
+    }
+
+    /**
+     * Execute a batch of SQL INSERT, UPDATE, or DELETE queries.
+     *
+     * @param conn The Connection to use to run the query.  The caller is
+     * responsible for closing this Connection.
+     * @param sql The SQL to execute.
+     * @param params An array of query replacement parameters.  Each row in
+     * this array is one set of batch replacement values.
+     * @return The number of rows updated per statement.
+     * @throws SQLException if a database access error occurs
+     * @since 1.1
+     */
+    public int[] batch(final Connection conn, final String sql, final Object[][] params) throws SQLException {
+        if (conn == null) {
+            throw new SQLException("Null connection");
+        }
+
+        if (sql == null) {
+            throw new SQLException("Null SQL statement");
+        }
+
+        if (params == null) {
+            throw new SQLException("Null parameters. If parameters aren't need, pass an empty array.");
+        }
+
+        PreparedStatement stmt = null;
+        ParameterMetaData pmd = null;
+        int[] rows = null;
+        try {
+            stmt = this.prepareStatement(conn, sql);
+            // When the batch size is large, prefetching parameter metadata before filling
+            // the statement can reduce lots of JDBC communications.
+            pmd = getParameterMetaData(stmt);
+
+            for (final Object[] param : params) {
+                this.fillStatement(stmt, pmd, param);
+                stmt.addBatch();
+            }
+            rows = stmt.executeBatch();
+
+        } catch (final SQLException e) {
+            rethrow(e, sql, (Object[])params);
+        } finally {
+            close(stmt);
+        }
+
+        return rows;
+    }
+
+    /**
+     * Execute a batch of SQL INSERT, UPDATE, or DELETE queries.  The
+     * {@code Connection} is retrieved from the {@code DataSource}
+     * set in the constructor.  This {@code Connection} must be in
+     * auto-commit mode or the update will not be saved.
+     *
+     * @param sql The SQL to execute.
+     * @param params An array of query replacement parameters.  Each row in
+     * this array is one set of batch replacement values.
+     * @return The number of rows updated per statement.
+     * @throws SQLException if a database access error occurs
+     * @since 1.1
+     */
+    public int[] batch(final String sql, final Object[][] params) throws SQLException {
+        try (Connection conn = prepareConnection()) {
+            return this.batch(conn, sql, params);
+        }
+    }
+
+    /**
+     * Execute an SQL statement, including a stored procedure call, which does
+     * not return any result sets.
+     * Any parameters which are instances of {@link OutParameter} will be
+     * registered as OUT parameters.
+     * <p>
+     * Use this method when invoking a stored procedure with OUT parameters
+     * that does not return any result sets.  If you are not invoking a stored
+     * procedure, or the stored procedure has no OUT parameters, consider using
+     * {@link #update(java.sql.Connection, String, Object...) }.
+     * If the stored procedure returns result sets, use
+     * {@link #execute(java.sql.Connection, String, org.apache.commons.dbutils.ResultSetHandler, Object...) }.
+     *
+     * @param conn The connection to use to run the query.
+     * @param sql The SQL to execute.
+     * @param params The query replacement parameters.
+     * @return The number of rows updated.
+     * @throws SQLException if a database access error occurs
+     */
+    public int execute(final Connection conn, final String sql, final Object... params) throws SQLException {
+        if (conn == null) {
+            throw new SQLException("Null connection");
+        }
+
+        if (sql == null) {
+            throw new SQLException("Null SQL statement");
+        }
+
+        CallableStatement stmt = null;
+        int rows = 0;
+
+        try {
+            stmt = prepareCall(conn, sql);
+            this.fillStatement(stmt, params);
+            stmt.execute();
+            rows = stmt.getUpdateCount();
+            retrieveOutParameters(stmt, params);
+
+        } catch (final SQLException e) {
+            rethrow(e, sql, params);
+
+        } finally {
+            close(stmt);
+        }
+
+        return rows;
+    }
+
+    /**
+     * Execute an SQL statement, including a stored procedure call, which
+     * returns one or more result sets.
+     * Any parameters which are instances of {@link OutParameter} will be
+     * registered as OUT parameters.
+     * <p>
+     * Use this method when: a) running SQL statements that return multiple
+     * result sets; b) invoking a stored procedure that return result
+     * sets and OUT parameters.  Otherwise you may wish to use
+     * {@link #query(java.sql.Connection, String, org.apache.commons.dbutils.ResultSetHandler, Object...) }
+     * (if there are no OUT parameters) or
+     * {@link #execute(java.sql.Connection, String, Object...) }
+     * (if there are no result sets).
+     *
+     * @param <T> The type of object that the handler returns
+     * @param conn The connection to use to run the query.
+     * @param sql The SQL to execute.
+     * @param rsh The result set handler
+     * @param params The query replacement parameters.
+     * @return A list of objects generated by the handler
+     * @throws SQLException if a database access error occurs
+     */
+    public <T> List<T> execute(final Connection conn, final String sql, final ResultSetHandler<T> rsh, final Object... params) throws SQLException {
+        if (conn == null) {
+            throw new SQLException("Null connection");
+        }
+
+        if (sql == null) {
+            throw new SQLException("Null SQL statement");
+        }
+
+        if (rsh == null) {
+            throw new SQLException("Null ResultSetHandler");
+        }
+
+        CallableStatement stmt = null;
+        final List<T> results = new LinkedList<>();
+
+        try {
+            stmt = prepareCall(conn, sql);
+            this.fillStatement(stmt, params);
+            boolean moreResultSets = stmt.execute();
+            // Handle multiple result sets by passing them through the handler
+            // retaining the final result
+            while (moreResultSets) {
+                try (@SuppressWarnings("resource")
+                // assume the ResultSet wrapper properly closes
+                ResultSet resultSet = wrap(stmt.getResultSet())) {
+                    results.add(rsh.handle(resultSet));
+                    moreResultSets = stmt.getMoreResults();
+                }
+            }
+            retrieveOutParameters(stmt, params);
+
+        } catch (final SQLException e) {
+            rethrow(e, sql, params);
+
+        } finally {
+            close(stmt);
+        }
+
+        return results;
+    }
+
+    /**
+     * Execute an SQL statement, including a stored procedure call, which does
+     * not return any result sets.
+     * Any parameters which are instances of {@link OutParameter} will be
+     * registered as OUT parameters.
+     * <p>
+     * Use this method when invoking a stored procedure with OUT parameters
+     * that does not return any result sets.  If you are not invoking a stored
+     * procedure, or the stored procedure has no OUT parameters, consider using
+     * {@link #update(String, Object...) }.
+     * If the stored procedure returns result sets, use
+     * {@link #execute(String, org.apache.commons.dbutils.ResultSetHandler, Object...) }.
+     * <p>
+     * The {@code Connection} is retrieved from the {@code DataSource}
+     * set in the constructor.  This {@code Connection} must be in
+     * auto-commit mode or the update will not be saved.
+     *
+     * @param sql The SQL statement to execute.
+     * @param params Initializes the CallableStatement's parameters (i.e. '?').
+     * @throws SQLException if a database access error occurs
+     * @return The number of rows updated.
+     */
+    public int execute(final String sql, final Object... params) throws SQLException {
+        try (Connection conn = prepareConnection()) {
+            return this.execute(conn, sql, params);
+        }
+    }
+
+    /**
+     * Execute an SQL statement, including a stored procedure call, which
+     * returns one or more result sets.
+     * Any parameters which are instances of {@link OutParameter} will be
+     * registered as OUT parameters.
+     * <p>
+     * Use this method when: a) running SQL statements that return multiple
+     * result sets; b) invoking a stored procedure that return result
+     * sets and OUT parameters.  Otherwise you may wish to use
+     * {@link #query(String, org.apache.commons.dbutils.ResultSetHandler, Object...) }
+     * (if there are no OUT parameters) or
+     * {@link #execute(String, Object...) }
+     * (if there are no result sets).
+     *
+     * @param <T> The type of object that the handler returns
+     * @param sql The SQL to execute.
+     * @param rsh The result set handler
+     * @param params The query replacement parameters.
+     * @return A list of objects generated by the handler
+     * @throws SQLException if a database access error occurs
+     */
+    public <T> List<T> execute(final String sql, final ResultSetHandler<T> rsh, final Object... params) throws SQLException {
+        try (Connection conn = prepareConnection()) {
+            return this.execute(conn, sql, rsh, params);
+        }
+    }
+
+    /**
+     * Execute an SQL INSERT query without replacement parameters.
+     * @param <T> The type of object that the handler returns
+     * @param conn The connection to use to run the query.
+     * @param sql The SQL to execute.
+     * @param rsh The handler used to create the result object from
+     * the {@code ResultSet} of auto-generated keys.
+     * @return An object generated by the handler.
+     * @throws SQLException if a database access error occurs
+     * @since 1.6
+     */
+    public <T> T insert(final Connection conn, final String sql, final ResultSetHandler<T> rsh) throws SQLException {
+        return insert(conn, sql, rsh, (Object[]) null);
+    }
+
+    /**
+     * Execute an SQL INSERT query.
+     * @param <T> The type of object that the handler returns
+     * @param conn The connection to use to run the query.
+     * @param sql The SQL to execute.
+     * @param rsh The handler used to create the result object from
+     * the {@code ResultSet} of auto-generated keys.
+     * @param params The query replacement parameters.
+     * @return An object generated by the handler.
+     * @throws SQLException if a database access error occurs
+     * @since 1.6
+     */
+    public <T> T insert(final Connection conn, final String sql, final ResultSetHandler<T> rsh, final Object... params) throws SQLException {
+        if (conn == null) {
+            throw new SQLException("Null connection");
+        }
+
+        if (sql == null) {
+            throw new SQLException("Null SQL statement");
+        }
+
+        if (rsh == null) {
+            throw new SQLException("Null ResultSetHandler");
+        }
+
+        Statement stmt = null;
+        T generatedKeys = null;
+
+        try {
+            if (params != null && params.length > 0) {
+                final PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
+                stmt = ps;
+                this.fillStatement(ps, params);
+                ps.executeUpdate();
+            } else {
+                stmt = conn.createStatement();
+                stmt.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
+            }
+            try (ResultSet resultSet = stmt.getGeneratedKeys()) {
+                generatedKeys = rsh.handle(resultSet);
+            }
+        } catch (final SQLException e) {
+            rethrow(e, sql, params);
+        } finally {
+            close(stmt);
+        }
+
+        return generatedKeys;
+    }
+
+    /**
+     * Executes the given INSERT SQL without any replacement parameters.
+     * The {@code Connection} is retrieved from the
+     * {@code DataSource} set in the constructor.
+     * @param <T> The type of object that the handler returns
+     * @param sql The SQL statement to execute.
+     * @param rsh The handler used to create the result object from
+     * the {@code ResultSet} of auto-generated keys.
+     * @return An object generated by the handler.
+     * @throws SQLException if a database access error occurs
+     * @since 1.6
+     */
+    public <T> T insert(final String sql, final ResultSetHandler<T> rsh) throws SQLException {
+        try (Connection conn = prepareConnection()) {
+            return insert(conn, sql, rsh, (Object[]) null);
+        }
+    }
+
+    /**
+     * Executes the given INSERT SQL statement. The
+     * {@code Connection} is retrieved from the {@code DataSource}
+     * set in the constructor.  This {@code Connection} must be in
+     * auto-commit mode or the insert will not be saved.
+     * @param <T> The type of object that the handler returns
+     * @param sql The SQL statement to execute.
+     * @param rsh The handler used to create the result object from
+     * the {@code ResultSet} of auto-generated keys.
+     * @param params Initializes the PreparedStatement's IN (i.e. '?')
+     * @return An object generated by the handler.
+     * @throws SQLException if a database access error occurs
+     * @since 1.6
+     */
+    public <T> T insert(final String sql, final ResultSetHandler<T> rsh, final Object... params) throws SQLException {
+        try (Connection conn = prepareConnection()) {
+            return insert(conn, sql, rsh, params);
+        }
+    }
+
+    /**
+     * Executes the given batch of INSERT SQL statements.
+     * @param <T> The type of object that the handler returns
+     * @param conn The connection to use to run the query.
+     * @param sql The SQL to execute.
+     * @param rsh The handler used to create the result object from
+     * the {@code ResultSet} of auto-generated keys.
+     * @param params The query replacement parameters.
+     * @return The result generated by the handler.
+     * @throws SQLException if a database access error occurs
+     * @since 1.6
+     */
+    public <T> T insertBatch(final Connection conn, final String sql, final ResultSetHandler<T> rsh, final Object[][] params) throws SQLException {
+        if (conn == null) {
+            throw new SQLException("Null connection");
+        }
+
+        if (sql == null) {
+            throw new SQLException("Null SQL statement");
+        }
+
+        if (params == null) {
+            throw new SQLException("Null parameters. If parameters aren't need, pass an empty array.");
+        }
+
+        PreparedStatement stmt = null;
+        T generatedKeys = null;
+        try {
+            stmt = this.prepareStatement(conn, sql, Statement.RETURN_GENERATED_KEYS);
+
+            for (final Object[] param : params) {
+                this.fillStatement(stmt, param);
+                stmt.addBatch();
+            }
+            stmt.executeBatch();
+            try (ResultSet resultSet = stmt.getGeneratedKeys()) {
+                generatedKeys = rsh.handle(resultSet);
+            }
+        } catch (final SQLException e) {
+            rethrow(e, sql, (Object[])params);
+        } finally {
+            close(stmt);
+        }
+
+        return generatedKeys;
+    }
+
+    /**
+     * Executes the given batch of INSERT SQL statements. The
+     * {@code Connection} is retrieved from the {@code DataSource}
+     * set in the constructor.  This {@code Connection} must be in
+     * auto-commit mode or the insert will not be saved.
+     * @param <T> The type of object that the handler returns
+     * @param sql The SQL statement to execute.
+     * @param rsh The handler used to create the result object from
+     * the {@code ResultSet} of auto-generated keys.
+     * @param params Initializes the PreparedStatement's IN (i.e. '?')
+     * @return The result generated by the handler.
+     * @throws SQLException if a database access error occurs
+     * @since 1.6
+     */
+    public <T> T insertBatch(final String sql, final ResultSetHandler<T> rsh, final Object[][] params) throws SQLException {
+        try (Connection conn = prepareConnection()) {
+            return insertBatch(conn, sql, rsh, params);
+        }
+    }
+
+    /**
+     * Execute an SQL SELECT query with a single replacement parameter. The
+     * caller is responsible for closing the connection.
+     * @param <T> The type of object that the handler returns
+     * @param conn The connection to execute the query in.
+     * @param sql The query to execute.
+     * @param param The replacement parameter.
+     * @param rsh The handler that converts the results into an object.
+     * @return The object returned by the handler.
+     * @throws SQLException if a database access error occurs
+     * @deprecated Use {@link #query(Connection, String, ResultSetHandler, Object...)}
+     */
+    @Deprecated
+    public <T> T query(final Connection conn, final String sql, final Object param, final ResultSetHandler<T> rsh) throws SQLException {
+        return this.<T>query(conn, sql, rsh, param);
+    }
+
+    /**
+     * Execute an SQL SELECT query with replacement parameters.  The
+     * caller is responsible for closing the connection.
+     * @param <T> The type of object that the handler returns
+     * @param conn The connection to execute the query in.
+     * @param sql The query to execute.
+     * @param params The replacement parameters.
+     * @param rsh The handler that converts the results into an object.
+     * @return The object returned by the handler.
+     * @throws SQLException if a database access error occurs
+     * @deprecated Use {@link #query(Connection,String,ResultSetHandler,Object...)} instead
+     */
+    @Deprecated
+    public <T> T query(final Connection conn, final String sql, final Object[] params, final ResultSetHandler<T> rsh) throws SQLException {
+        return this.<T>query(conn, sql, rsh, params);
+    }
+
+    /**
+     * Execute an SQL SELECT query without any replacement parameters.  The
+     * caller is responsible for closing the connection.
+     * @param <T> The type of object that the handler returns
+     * @param conn The connection to execute the query in.
+     * @param sql The query to execute.
+     * @param rsh The handler that converts the results into an object.
+     * @return The object returned by the handler.
+     * @throws SQLException if a database access error occurs
+     */
+    public <T> T query(final Connection conn, final String sql, final ResultSetHandler<T> rsh) throws SQLException {
+        return this.<T>query(conn, sql, rsh, (Object[]) null);
+    }
+
+    /**
+     * Execute an SQL SELECT query with replacement parameters.  The
+     * caller is responsible for closing the connection.
+     * @param <T> The type of object that the handler returns
+     * @param conn The connection to execute the query in.
+     * @param sql The query to execute.
+     * @param rsh The handler that converts the results into an object.
+     * @param params The replacement parameters.
+     * @return The object returned by the handler.
+     * @throws SQLException if a database access error occurs
+     */
+    public <T> T query(final Connection conn, final String sql, final ResultSetHandler<T> rsh, final Object... params) throws SQLException {
+        if (conn == null) {
+            throw new SQLException("Null connection");
+        }
+
+        if (sql == null) {
+            throw new SQLException("Null SQL statement");
+        }
+
+        if (rsh == null) {
+            throw new SQLException("Null ResultSetHandler");
+        }
+
+        Statement stmt = null;
+        ResultSet resultSet = null;
+        T result = null;
+
+        try {
+            if (params != null && params.length > 0) {
+                final PreparedStatement ps = this.prepareStatement(conn, sql);
+                stmt = ps;
+                this.fillStatement(ps, params);
+                resultSet = wrap(ps.executeQuery());
+            } else {
+                stmt = conn.createStatement();
+                resultSet = wrap(stmt.executeQuery(sql));
+            }
+            result = rsh.handle(resultSet);
+
+        } catch (final SQLException e) {
+            rethrow(e, sql, params);
+
+        } finally {
+            closeQuietly(resultSet);
+            closeQuietly(stmt);
+        }
+
+        return result;
+    }
+
+    /**
+     * Executes the given SELECT SQL with a single replacement parameter.
+     * The {@code Connection} is retrieved from the
+     * {@code DataSource} set in the constructor.
+     * @param <T> The type of object that the handler returns
+     * @param sql The SQL statement to execute.
+     * @param param The replacement parameter.
+     * @param rsh The handler used to create the result object from
+     * the {@code ResultSet}.
+     *
+     * @return An object generated by the handler.
+     * @throws SQLException if a database access error occurs
+     * @deprecated Use {@link #query(String, ResultSetHandler, Object...)}
+     */
+    @Deprecated
+    public <T> T query(final String sql, final Object param, final ResultSetHandler<T> rsh) throws SQLException {
+        try (Connection conn = prepareConnection()) {
+            return this.<T>query(conn, sql, rsh, param);
+        }
+    }
+
+    /**
+     * Executes the given SELECT SQL query and returns a result object.
+     * The {@code Connection} is retrieved from the
+     * {@code DataSource} set in the constructor.
+     * @param <T> The type of object that the handler returns
+     * @param sql The SQL statement to execute.
+     * @param params Initialize the PreparedStatement's IN parameters with
+     * this array.
+     *
+     * @param rsh The handler used to create the result object from
+     * the {@code ResultSet}.
+     *
+     * @return An object generated by the handler.
+     * @throws SQLException if a database access error occurs
+     * @deprecated Use {@link #query(String, ResultSetHandler, Object...)}
+     */
+    @Deprecated
+    public <T> T query(final String sql, final Object[] params, final ResultSetHandler<T> rsh) throws SQLException {
+        try (Connection conn = prepareConnection()) {
+            return this.<T>query(conn, sql, rsh, params);
+        }
+    }
+
+    /**
+     * Executes the given SELECT SQL without any replacement parameters.
+     * The {@code Connection} is retrieved from the
+     * {@code DataSource} set in the constructor.
+     * @param <T> The type of object that the handler returns
+     * @param sql The SQL statement to execute.
+     * @param rsh The handler used to create the result object from
+     * the {@code ResultSet}.
+     *
+     * @return An object generated by the handler.
+     * @throws SQLException if a database access error occurs
+     */
+    public <T> T query(final String sql, final ResultSetHandler<T> rsh) throws SQLException {
+        try (Connection conn = prepareConnection()) {
+            return this.<T>query(conn, sql, rsh, (Object[]) null);
+        }
+    }
+
+    /**
+     * Executes the given SELECT SQL query and returns a result object.
+     * The {@code Connection} is retrieved from the
+     * {@code DataSource} set in the constructor.
+     * @param <T> The type of object that the handler returns
+     * @param sql The SQL statement to execute.
+     * @param rsh The handler used to create the result object from
+     * the {@code ResultSet}.
+     * @param params Initialize the PreparedStatement's IN parameters with
+     * this array.
+     * @return An object generated by the handler.
+     * @throws SQLException if a database access error occurs
+     */
+    public <T> T query(final String sql, final ResultSetHandler<T> rsh, final Object... params) throws SQLException {
+        try (Connection conn = prepareConnection()) {
+            return this.<T>query(conn, sql, rsh, params);
+        }
+    }
+
+    /**
+     * Set the value on all the {@link OutParameter} instances in the
+     * {@code params} array using the OUT parameter values from the
+     * {@code stmt}.
+     * @param stmt the statement from which to retrieve OUT parameter values
+     * @param params the parameter array for the statement invocation
+     * @throws SQLException when the value could not be retrieved from the
+     * statement.
+     */
+    private void retrieveOutParameters(final CallableStatement stmt, final Object[] params) throws SQLException {
+        if (params != null) {
+            for (int i = 0; i < params.length; i++) {
+                if (params[i] instanceof OutParameter) {
+                    ((OutParameter<?>) params[i]).setValue(stmt, i + 1);
+                }
+            }
+        }
+    }
+
+    /**
+     * Execute an SQL INSERT, UPDATE, or DELETE query without replacement
+     * parameters.
+     *
+     * @param conn The connection to use to run the query.
+     * @param sql The SQL to execute.
+     * @return The number of rows updated.
+     * @throws SQLException if a database access error occurs
+     */
+    public int update(final Connection conn, final String sql) throws SQLException {
+        return this.update(conn, sql, (Object[]) null);
+    }
+
+    /**
+     * Execute an SQL INSERT, UPDATE, or DELETE query with a single replacement
+     * parameter.
+     *
+     * @param conn The connection to use to run the query.
+     * @param sql The SQL to execute.
+     * @param param The replacement parameter.
+     * @return The number of rows updated.
+     * @throws SQLException if a database access error occurs
+     */
+    public int update(final Connection conn, final String sql, final Object param) throws SQLException {
+        return this.update(conn, sql, new Object[] { param });
+    }
+
+    /**
+     * Execute an SQL INSERT, UPDATE, or DELETE query.
+     *
+     * @param conn The connection to use to run the query.
+     * @param sql The SQL to execute.
+     * @param params The query replacement parameters.
+     * @return The number of rows updated.
+     * @throws SQLException if a database access error occurs
+     */
+    public int update(final Connection conn, final String sql, final Object... params) throws SQLException {
+        if (conn == null) {
+            throw new SQLException("Null connection");
+        }
+
+        if (sql == null) {
+            throw new SQLException("Null SQL statement");
+        }
+
+        Statement stmt = null;
+        int rows = 0;
+
+        try {
+            if (params != null && params.length > 0) {
+                final PreparedStatement ps = this.prepareStatement(conn, sql);
+                stmt = ps;
+                this.fillStatement(ps, params);
+                rows = ps.executeUpdate();
+            } else {
+                stmt = conn.createStatement();
+                rows = stmt.executeUpdate(sql);
+            }
+
+        } catch (final SQLException e) {
+            rethrow(e, sql, params);
+
+        } finally {
+            close(stmt);
+        }
+
+        return rows;
+    }
+
+    /**
+     * Executes the given INSERT, UPDATE, or DELETE SQL statement without
+     * any replacement parameters. The {@code Connection} is retrieved
+     * from the {@code DataSource} set in the constructor.  This
+     * {@code Connection} must be in auto-commit mode or the update will
+     * not be saved.
+     *
+     * @param sql The SQL statement to execute.
+     * @throws SQLException if a database access error occurs
+     * @return The number of rows updated.
+     */
+    public int update(final String sql) throws SQLException {
+        try (Connection conn = prepareConnection()) {
+            return this.update(conn, sql, (Object[]) null);
+        }
+    }
+
+    /**
+     * Executes the given INSERT, UPDATE, or DELETE SQL statement with
+     * a single replacement parameter.  The {@code Connection} is
+     * retrieved from the {@code DataSource} set in the constructor.
+     * This {@code Connection} must be in auto-commit mode or the
+     * update will not be saved.
+     *
+     * @param sql The SQL statement to execute.
+     * @param param The replacement parameter.
+     * @throws SQLException if a database access error occurs
+     * @return The number of rows updated.
+     */
+    public int update(final String sql, final Object param) throws SQLException {
+        try (Connection conn = prepareConnection()) {
+            return this.update(conn, sql, param);
+        }
+    }
+
+    /**
+     * Executes the given INSERT, UPDATE, or DELETE SQL statement.  The
+     * {@code Connection} is retrieved from the {@code DataSource}
+     * set in the constructor.  This {@code Connection} must be in
+     * auto-commit mode or the update will not be saved.
+     *
+     * @param sql The SQL statement to execute.
+     * @param params Initializes the PreparedStatement's IN (i.e. '?')
+     * parameters.
+     * @throws SQLException if a database access error occurs
+     * @return The number of rows updated.
+     */
+    public int update(final String sql, final Object... params) throws SQLException {
+        try (Connection conn = prepareConnection()) {
+            return this.update(conn, sql, params);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/dbutils/ResultSetIterator.java b/src/main/java/org/apache/commons/dbutils/ResultSetIterator.java
index 5f776ff..8bd9f41 100644
--- a/src/main/java/org/apache/commons/dbutils/ResultSetIterator.java
+++ b/src/main/java/org/apache/commons/dbutils/ResultSetIterator.java
@@ -1,133 +1,133 @@
-/*

- * 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.dbutils;

-

-import java.sql.ResultSet;

-import java.sql.SQLException;

-import java.util.Iterator;

-

-/**

- * <p>

- * Wraps a {@code ResultSet} in an {@code Iterator&lt;Object[]&gt;}.  This is useful

- * when you want to present a non-database application layer with domain

- * neutral data.

- * </p>

- *

- * <p>

- * This implementation requires the {@code ResultSet.isLast()} method

- * to be implemented.

- * </p>

- */

-public class ResultSetIterator implements Iterator<Object[]> {

-

-    /**

-     * Generates an {@code Iterable}, suitable for use in for-each loops.

-     *

-     * @param resultSet Wrap this {@code ResultSet} in an {@code Iterator}.

-     * @return an {@code Iterable}, suitable for use in for-each loops.

-     */

-    public static Iterable<Object[]> iterable(final ResultSet resultSet) {

-        return () -> new ResultSetIterator(resultSet);

-    }

-

-    /**

-     * The wrapped {@code ResultSet}.

-     */

-    private final ResultSet resultSet;

-

-    /**

-     * The processor to use when converting a row into an Object[].

-     */

-    private final RowProcessor convert;

-

-    /**

-     * Constructor for ResultSetIterator.

-     * @param resultSet Wrap this {@code ResultSet} in an {@code Iterator}.

-     */

-    public ResultSetIterator(final ResultSet resultSet) {

-        this(resultSet, new BasicRowProcessor());

-    }

-

-    /**

-     * Constructor for ResultSetIterator.

-     * @param resultSet Wrap this {@code ResultSet} in an {@code Iterator}.

-     * @param convert The processor to use when converting a row into an

-     * {@code Object[]}.  Defaults to a

-     * {@code BasicRowProcessor}.

-     */

-    public ResultSetIterator(final ResultSet resultSet, final RowProcessor convert) {

-        this.resultSet = resultSet;

-        this.convert = convert;

-    }

-

-    /**

-     * Returns true if there are more rows in the ResultSet.

-     * @return boolean {@code true} if there are more rows

-     * @throws RuntimeException if an SQLException occurs.

-     */

-    @Override

-    public boolean hasNext() {

-        try {

-            return !resultSet.isLast();

-        } catch (final SQLException e) {

-            rethrow(e);

-            return false;

-        }

-    }

-

-    /**

-     * Returns the next row as an {@code Object[]}.

-     * @return An {@code Object[]} with the same number of elements as

-     * columns in the {@code ResultSet}.

-     * @see java.util.Iterator#next()

-     * @throws RuntimeException if an SQLException occurs.

-     */

-    @Override

-    public Object[] next() {

-        try {

-            return resultSet.next() ? this.convert.toArray(resultSet) : new Object[0];

-        } catch (final SQLException e) {

-            rethrow(e);

-            return null;

-        }

-    }

-

-    /**

-     * Deletes the current row from the {@code ResultSet}.

-     * @see java.util.Iterator#remove()

-     * @throws RuntimeException if an SQLException occurs.

-     */

-    @Override

-    public void remove() {

-        try {

-            this.resultSet.deleteRow();

-        } catch (final SQLException e) {

-            rethrow(e);

-        }

-    }

-

-    /**

-     * Rethrow the SQLException as a RuntimeException.  This implementation

-     * creates a new RuntimeException with the SQLException's error message.

-     * @param e SQLException to rethrow

-     * @since 1.1

-     */

-    protected void rethrow(final SQLException e) {

-        throw new RuntimeException(e.getMessage());

-    }

-

-}

+/*
+ * 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.dbutils;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Iterator;
+
+/**
+ * <p>
+ * Wraps a {@code ResultSet} in an {@code Iterator&lt;Object[]&gt;}.  This is useful
+ * when you want to present a non-database application layer with domain
+ * neutral data.
+ * </p>
+ *
+ * <p>
+ * This implementation requires the {@code ResultSet.isLast()} method
+ * to be implemented.
+ * </p>
+ */
+public class ResultSetIterator implements Iterator<Object[]> {
+
+    /**
+     * Generates an {@code Iterable}, suitable for use in for-each loops.
+     *
+     * @param resultSet Wrap this {@code ResultSet} in an {@code Iterator}.
+     * @return an {@code Iterable}, suitable for use in for-each loops.
+     */
+    public static Iterable<Object[]> iterable(final ResultSet resultSet) {
+        return () -> new ResultSetIterator(resultSet);
+    }
+
+    /**
+     * The wrapped {@code ResultSet}.
+     */
+    private final ResultSet resultSet;
+
+    /**
+     * The processor to use when converting a row into an Object[].
+     */
+    private final RowProcessor convert;
+
+    /**
+     * Constructor for ResultSetIterator.
+     * @param resultSet Wrap this {@code ResultSet} in an {@code Iterator}.
+     */
+    public ResultSetIterator(final ResultSet resultSet) {
+        this(resultSet, new BasicRowProcessor());
+    }
+
+    /**
+     * Constructor for ResultSetIterator.
+     * @param resultSet Wrap this {@code ResultSet} in an {@code Iterator}.
+     * @param convert The processor to use when converting a row into an
+     * {@code Object[]}.  Defaults to a
+     * {@code BasicRowProcessor}.
+     */
+    public ResultSetIterator(final ResultSet resultSet, final RowProcessor convert) {
+        this.resultSet = resultSet;
+        this.convert = convert;
+    }
+
+    /**
+     * Returns true if there are more rows in the ResultSet.
+     * @return boolean {@code true} if there are more rows
+     * @throws RuntimeException if an SQLException occurs.
+     */
+    @Override
+    public boolean hasNext() {
+        try {
+            return !resultSet.isLast();
+        } catch (final SQLException e) {
+            rethrow(e);
+            return false;
+        }
+    }
+
+    /**
+     * Returns the next row as an {@code Object[]}.
+     * @return An {@code Object[]} with the same number of elements as
+     * columns in the {@code ResultSet}.
+     * @see java.util.Iterator#next()
+     * @throws RuntimeException if an SQLException occurs.
+     */
+    @Override
+    public Object[] next() {
+        try {
+            return resultSet.next() ? this.convert.toArray(resultSet) : new Object[0];
+        } catch (final SQLException e) {
+            rethrow(e);
+            return null;
+        }
+    }
+
+    /**
+     * Deletes the current row from the {@code ResultSet}.
+     * @see java.util.Iterator#remove()
+     * @throws RuntimeException if an SQLException occurs.
+     */
+    @Override
+    public void remove() {
+        try {
+            this.resultSet.deleteRow();
+        } catch (final SQLException e) {
+            rethrow(e);
+        }
+    }
+
+    /**
+     * Rethrow the SQLException as a RuntimeException.  This implementation
+     * creates a new RuntimeException with the SQLException's error message.
+     * @param e SQLException to rethrow
+     * @since 1.1
+     */
+    protected void rethrow(final SQLException e) {
+        throw new RuntimeException(e.getMessage());
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/dbutils/handlers/AbstractKeyedHandler.java b/src/main/java/org/apache/commons/dbutils/handlers/AbstractKeyedHandler.java
index 8f7b0d5..c51381f 100644
--- a/src/main/java/org/apache/commons/dbutils/handlers/AbstractKeyedHandler.java
+++ b/src/main/java/org/apache/commons/dbutils/handlers/AbstractKeyedHandler.java
@@ -1,86 +1,86 @@
-/*

- * 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.dbutils.handlers;

-

-import java.sql.ResultSet;

-import java.sql.SQLException;

-import java.util.HashMap;

-import java.util.Map;

-

-import org.apache.commons.dbutils.ResultSetHandler;

-

-/**

- * <p>

- * {@code ResultSetHandler} implementation that returns a Map.

- * {@code ResultSet} rows are converted into objects (Vs) which are then stored

- * in a Map under the given keys (Ks).

- * </p>

- *

- * @param <K> the type of keys maintained by the returned map

- * @param <V> the type of mapped values

- * @see org.apache.commons.dbutils.ResultSetHandler

- * @since 1.3

- */

-public abstract class AbstractKeyedHandler<K, V> implements ResultSetHandler<Map<K, V>> {

-

-    /**

-     * This factory method is called by {@code handle()} to retrieve the

-     * key value from the current {@code ResultSet} row.

-     * @param resultSet ResultSet to create a key from

-     * @return K from the configured key column name/index

-     * @throws SQLException if a database access error occurs

-     */

-    protected abstract K createKey(ResultSet resultSet) throws SQLException;

-

-    /**

-     * This factory method is called by {@code handle()} to create the Map

-     * to store records in.  This implementation returns a {@code HashMap}

-     * instance.

-     *

-     * @return Map to store records in

-     */

-    protected Map<K, V> createMap() {

-        return new HashMap<>();

-    }

-

-    /**

-     * This factory method is called by {@code handle()} to store the

-     * current {@code ResultSet} row in some object.

-     * @param resultSet ResultSet to create a row from

-     * @return V object created from the current row

-     * @throws SQLException if a database access error occurs

-     */

-    protected abstract V createRow(ResultSet resultSet) throws SQLException;

-

-    /**

-     * Convert each row's columns into a Map and store then

-     * in a {@code Map} under {@code ResultSet.getObject(key)} key.

-     * @param resultSet {@code ResultSet} to process.

-     * @return A {@code Map}, never {@code null}.

-     * @throws SQLException if a database access error occurs

-     * @see org.apache.commons.dbutils.ResultSetHandler#handle(java.sql.ResultSet)

-     */

-    @Override

-    public Map<K, V> handle(final ResultSet resultSet) throws SQLException {

-        final Map<K, V> result = createMap();

-        while (resultSet.next()) {

-            result.put(createKey(resultSet), createRow(resultSet));

-        }

-        return result;

-    }

-

-}

+/*
+ * 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.dbutils.handlers;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.dbutils.ResultSetHandler;
+
+/**
+ * <p>
+ * {@code ResultSetHandler} implementation that returns a Map.
+ * {@code ResultSet} rows are converted into objects (Vs) which are then stored
+ * in a Map under the given keys (Ks).
+ * </p>
+ *
+ * @param <K> the type of keys maintained by the returned map
+ * @param <V> the type of mapped values
+ * @see org.apache.commons.dbutils.ResultSetHandler
+ * @since 1.3
+ */
+public abstract class AbstractKeyedHandler<K, V> implements ResultSetHandler<Map<K, V>> {
+
+    /**
+     * This factory method is called by {@code handle()} to retrieve the
+     * key value from the current {@code ResultSet} row.
+     * @param resultSet ResultSet to create a key from
+     * @return K from the configured key column name/index
+     * @throws SQLException if a database access error occurs
+     */
+    protected abstract K createKey(ResultSet resultSet) throws SQLException;
+
+    /**
+     * This factory method is called by {@code handle()} to create the Map
+     * to store records in.  This implementation returns a {@code HashMap}
+     * instance.
+     *
+     * @return Map to store records in
+     */
+    protected Map<K, V> createMap() {
+        return new HashMap<>();
+    }
+
+    /**
+     * This factory method is called by {@code handle()} to store the
+     * current {@code ResultSet} row in some object.
+     * @param resultSet ResultSet to create a row from
+     * @return V object created from the current row
+     * @throws SQLException if a database access error occurs
+     */
+    protected abstract V createRow(ResultSet resultSet) throws SQLException;
+
+    /**
+     * Convert each row's columns into a Map and store then
+     * in a {@code Map} under {@code ResultSet.getObject(key)} key.
+     * @param resultSet {@code ResultSet} to process.
+     * @return A {@code Map}, never {@code null}.
+     * @throws SQLException if a database access error occurs
+     * @see org.apache.commons.dbutils.ResultSetHandler#handle(java.sql.ResultSet)
+     */
+    @Override
+    public Map<K, V> handle(final ResultSet resultSet) throws SQLException {
+        final Map<K, V> result = createMap();
+        while (resultSet.next()) {
+            result.put(createKey(resultSet), createRow(resultSet));
+        }
+        return result;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/dbutils/handlers/BeanMapHandler.java b/src/main/java/org/apache/commons/dbutils/handlers/BeanMapHandler.java
index 4e8c775..ad47ac7 100644
--- a/src/main/java/org/apache/commons/dbutils/handlers/BeanMapHandler.java
+++ b/src/main/java/org/apache/commons/dbutils/handlers/BeanMapHandler.java
@@ -1,181 +1,181 @@
-/*

- * 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.dbutils.handlers;

-

-import java.sql.ResultSet;

-import java.sql.SQLException;

-

-import org.apache.commons.dbutils.RowProcessor;

-

-/**

- * <p>

- * {@code ResultSetHandler} implementation that returns a Map of Beans.

- * {@code ResultSet} rows are converted into Beans which are then stored in

- * a Map under the given key.

- * </p>

- * <p>

- * If you had a Person table with a primary key column called ID, you could

- * retrieve rows from the table like this:

- *

- * <pre>

- * ResultSetHandler&lt;Map&lt;Long, Person&gt;&gt; h = new BeanMapHandler&lt;Long, Person&gt;(Person.class, &quot;id&quot;);

- * Map&lt;Long, Person&gt; found = queryRunner.query(&quot;select id, name, age from person&quot;, h);

- * Person jane = found.get(1L); // jane's id is 1

- * String janesName = jane.getName();

- * Integer janesAge = jane.getAge();

- * </pre>

- *

- * Note that the "id" passed to BeanMapHandler can be in any case. The data type

- * returned for id is dependent upon how your JDBC driver converts SQL column

- * types from the Person table into Java types. The "name" and "age" columns are

- * converted according to their property descriptors by DbUtils.

- * &lt;/p&gt;

- * <p>

- * This class is thread safe.

- * &lt;/p&gt;

- *

- * @param <K>

- *            the type of keys maintained by the returned map

- * @param <V>

- *            the type of the bean

- * @see org.apache.commons.dbutils.ResultSetHandler

- * @since 1.5

- */

-public class BeanMapHandler<K, V> extends AbstractKeyedHandler<K, V> {

-

-    /**

-     * The Class of beans produced by this handler.

-     */

-    private final Class<V> type;

-

-    /**

-     * The RowProcessor implementation to use when converting rows into Objects.

-     */

-    private final RowProcessor convert;

-

-    /**

-     * The column index to retrieve key values from. Defaults to 1.

-     */

-    private final int columnIndex;

-

-    /**

-     * The column name to retrieve key values from. Either columnName or

-     * columnIndex will be used but never both.

-     */

-    private final String columnName;

-

-    /**

-     * Creates a new instance of BeanMapHandler. The value of the first column

-     * of each row will be a key in the Map.

-     *

-     * @param type

-     *            The Class that objects returned from {@code createRow()}

-     *            are created from.

-     */

-    public BeanMapHandler(final Class<V> type) {

-        this(type, ArrayHandler.ROW_PROCESSOR, 1, null);

-    }

-

-    /**

-     * Creates a new instance of BeanMapHandler.

-     *

-     * @param type

-     *            The Class that objects returned from {@code createRow()}

-     *            are created from.

-     * @param columnIndex

-     *            The values to use as keys in the Map are retrieved from the

-     *            column at this index.

-     */

-    public BeanMapHandler(final Class<V> type, final int columnIndex) {

-        this(type, ArrayHandler.ROW_PROCESSOR, columnIndex, null);

-    }

-

-    /**

-     * Creates a new instance of BeanMapHandler. The value of the first column

-     * of each row will be a key in the Map.

-     *

-     * @param type

-     *            The Class that objects returned from {@code createRow()}

-     *            are created from.

-     * @param convert

-     *            The {@code RowProcessor} implementation to use when

-     *            converting rows into Beans

-     */

-    public BeanMapHandler(final Class<V> type, final RowProcessor convert) {

-        this(type, convert, 1, null);

-    }

-

-    /**

-     * Private Helper

-     *

-     * @param convert

-     *            The {@code RowProcessor} implementation to use when

-     *            converting rows into Beans

-     * @param columnIndex

-     *            The values to use as keys in the Map are retrieved from the

-     *            column at this index.

-     * @param columnName

-     *            The values to use as keys in the Map are retrieved from the

-     *            column with this name.

-     */

-    private BeanMapHandler(final Class<V> type, final RowProcessor convert,

-            final int columnIndex, final String columnName) {

-        this.type = type;

-        this.convert = convert;

-        this.columnIndex = columnIndex;

-        this.columnName = columnName;

-    }

-

-    /**

-     * Creates a new instance of BeanMapHandler.

-     *

-     * @param type

-     *            The Class that objects returned from {@code createRow()}

-     *            are created from.

-     * @param columnName

-     *            The values to use as keys in the Map are retrieved from the

-     *            column with this name.

-     */

-    public BeanMapHandler(final Class<V> type, final String columnName) {

-        this(type, ArrayHandler.ROW_PROCESSOR, 1, columnName);

-    }

-

-    /**

-     * This factory method is called by {@code handle()} to retrieve the

-     * key value from the current {@code ResultSet} row.

-     * @param resultSet ResultSet to create a key from

-     * @return K from the configured key column name/index

-     * @throws SQLException if a database access error occurs

-     * @throws ClassCastException if the class datatype does not match the column type

-     * @see org.apache.commons.dbutils.handlers.AbstractKeyedHandler#createKey(ResultSet)

-     */

-    // We assume that the user has picked the correct type to match the column

-    // so getObject will return the appropriate type and the cast will succeed.

-    @SuppressWarnings("unchecked")

-    @Override

-    protected K createKey(final ResultSet resultSet) throws SQLException {

-        return columnName == null ?

-               (K) resultSet.getObject(columnIndex) :

-               (K) resultSet.getObject(columnName);

-    }

-

-    @Override

-    protected V createRow(final ResultSet resultSet) throws SQLException {

-        return this.convert.toBean(resultSet, type);

-    }

-

-}

+/*
+ * 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.dbutils.handlers;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.apache.commons.dbutils.RowProcessor;
+
+/**
+ * <p>
+ * {@code ResultSetHandler} implementation that returns a Map of Beans.
+ * {@code ResultSet} rows are converted into Beans which are then stored in
+ * a Map under the given key.
+ * </p>
+ * <p>
+ * If you had a Person table with a primary key column called ID, you could
+ * retrieve rows from the table like this:
+ *
+ * <pre>
+ * ResultSetHandler&lt;Map&lt;Long, Person&gt;&gt; h = new BeanMapHandler&lt;Long, Person&gt;(Person.class, &quot;id&quot;);
+ * Map&lt;Long, Person&gt; found = queryRunner.query(&quot;select id, name, age from person&quot;, h);
+ * Person jane = found.get(1L); // jane's id is 1
+ * String janesName = jane.getName();
+ * Integer janesAge = jane.getAge();
+ * </pre>
+ *
+ * Note that the "id" passed to BeanMapHandler can be in any case. The data type
+ * returned for id is dependent upon how your JDBC driver converts SQL column
+ * types from the Person table into Java types. The "name" and "age" columns are
+ * converted according to their property descriptors by DbUtils.
+ * &lt;/p&gt;
+ * <p>
+ * This class is thread safe.
+ * &lt;/p&gt;
+ *
+ * @param <K>
+ *            the type of keys maintained by the returned map
+ * @param <V>
+ *            the type of the bean
+ * @see org.apache.commons.dbutils.ResultSetHandler
+ * @since 1.5
+ */
+public class BeanMapHandler<K, V> extends AbstractKeyedHandler<K, V> {
+
+    /**
+     * The Class of beans produced by this handler.
+     */
+    private final Class<V> type;
+
+    /**
+     * The RowProcessor implementation to use when converting rows into Objects.
+     */
+    private final RowProcessor convert;
+
+    /**
+     * The column index to retrieve key values from. Defaults to 1.
+     */
+    private final int columnIndex;
+
+    /**
+     * The column name to retrieve key values from. Either columnName or
+     * columnIndex will be used but never both.
+     */
+    private final String columnName;
+
+    /**
+     * Creates a new instance of BeanMapHandler. The value of the first column
+     * of each row will be a key in the Map.
+     *
+     * @param type
+     *            The Class that objects returned from {@code createRow()}
+     *            are created from.
+     */
+    public BeanMapHandler(final Class<V> type) {
+        this(type, ArrayHandler.ROW_PROCESSOR, 1, null);
+    }
+
+    /**
+     * Creates a new instance of BeanMapHandler.
+     *
+     * @param type
+     *            The Class that objects returned from {@code createRow()}
+     *            are created from.
+     * @param columnIndex
+     *            The values to use as keys in the Map are retrieved from the
+     *            column at this index.
+     */
+    public BeanMapHandler(final Class<V> type, final int columnIndex) {
+        this(type, ArrayHandler.ROW_PROCESSOR, columnIndex, null);
+    }
+
+    /**
+     * Creates a new instance of BeanMapHandler. The value of the first column
+     * of each row will be a key in the Map.
+     *
+     * @param type
+     *            The Class that objects returned from {@code createRow()}
+     *            are created from.
+     * @param convert
+     *            The {@code RowProcessor} implementation to use when
+     *            converting rows into Beans
+     */
+    public BeanMapHandler(final Class<V> type, final RowProcessor convert) {
+        this(type, convert, 1, null);
+    }
+
+    /**
+     * Private Helper
+     *
+     * @param convert
+     *            The {@code RowProcessor} implementation to use when
+     *            converting rows into Beans
+     * @param columnIndex
+     *            The values to use as keys in the Map are retrieved from the
+     *            column at this index.
+     * @param columnName
+     *            The values to use as keys in the Map are retrieved from the
+     *            column with this name.
+     */
+    private BeanMapHandler(final Class<V> type, final RowProcessor convert,
+            final int columnIndex, final String columnName) {
+        this.type = type;
+        this.convert = convert;
+        this.columnIndex = columnIndex;
+        this.columnName = columnName;
+    }
+
+    /**
+     * Creates a new instance of BeanMapHandler.
+     *
+     * @param type
+     *            The Class that objects returned from {@code createRow()}
+     *            are created from.
+     * @param columnName
+     *            The values to use as keys in the Map are retrieved from the
+     *            column with this name.
+     */
+    public BeanMapHandler(final Class<V> type, final String columnName) {
+        this(type, ArrayHandler.ROW_PROCESSOR, 1, columnName);
+    }
+
+    /**
+     * This factory method is called by {@code handle()} to retrieve the
+     * key value from the current {@code ResultSet} row.
+     * @param resultSet ResultSet to create a key from
+     * @return K from the configured key column name/index
+     * @throws SQLException if a database access error occurs
+     * @throws ClassCastException if the class datatype does not match the column type
+     * @see org.apache.commons.dbutils.handlers.AbstractKeyedHandler#createKey(ResultSet)
+     */
+    // We assume that the user has picked the correct type to match the column
+    // so getObject will return the appropriate type and the cast will succeed.
+    @SuppressWarnings("unchecked")
+    @Override
+    protected K createKey(final ResultSet resultSet) throws SQLException {
+        return columnName == null ?
+               (K) resultSet.getObject(columnIndex) :
+               (K) resultSet.getObject(columnName);
+    }
+
+    @Override
+    protected V createRow(final ResultSet resultSet) throws SQLException {
+        return this.convert.toBean(resultSet, type);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/dbutils/handlers/ColumnListHandler.java b/src/main/java/org/apache/commons/dbutils/handlers/ColumnListHandler.java
index 8d8dd2c..c4b94f4 100644
--- a/src/main/java/org/apache/commons/dbutils/handlers/ColumnListHandler.java
+++ b/src/main/java/org/apache/commons/dbutils/handlers/ColumnListHandler.java
@@ -1,102 +1,102 @@
-/*

- * 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.dbutils.handlers;

-

-import java.sql.ResultSet;

-import java.sql.SQLException;

-

-/**

- * {@code ResultSetHandler} implementation that converts one

- * {@code ResultSet} column into a {@code List} of

- * {@code Object}s. This class is thread safe.

- *

- * @param <T> The type of the column.

- * @see org.apache.commons.dbutils.ResultSetHandler

- * @since 1.1

- */

-public class ColumnListHandler<T> extends AbstractListHandler<T> {

-

-    /**

-     * The column number to retrieve.

-     */

-    private final int columnIndex;

-

-    /**

-     * The column name to retrieve.  Either columnName or columnIndex

-     * will be used but never both.

-     */

-    private final String columnName;

-

-    /**

-     * Creates a new instance of ColumnListHandler.  The first column of each

-     * row will be returned from {@code handle()}.

-     */

-    public ColumnListHandler() {

-        this(1, null);

-    }

-

-    /**

-     * Creates a new instance of ColumnListHandler.

-     *

-     * @param columnIndex The index of the column to retrieve from the

-     * {@code ResultSet}.

-     */

-    public ColumnListHandler(final int columnIndex) {

-        this(columnIndex, null);

-    }

-

-    /** Private Helper

-     * @param columnIndex The index of the column to retrieve from the

-     * {@code ResultSet}.

-     * @param columnName The name of the column to retrieve from the

-     * {@code ResultSet}.

-     */

-    private ColumnListHandler(final int columnIndex, final String columnName) {

-        this.columnIndex = columnIndex;

-        this.columnName = columnName;

-    }

-

-    /**

-     * Creates a new instance of ColumnListHandler.

-     *

-     * @param columnName The name of the column to retrieve from the

-     * {@code ResultSet}.

-     */

-    public ColumnListHandler(final String columnName) {

-        this(1, columnName);

-    }

-

-    /**

-     * Returns one {@code ResultSet} column value as {@code Object}.

-     * @param resultSet {@code ResultSet} to process.

-     * @return {@code Object}, never {@code null}.

-     * @throws SQLException if a database access error occurs

-     * @throws ClassCastException if the class datatype does not match the column type

-     * @see org.apache.commons.dbutils.handlers.AbstractListHandler#handle(ResultSet)

-     */

-    // We assume that the user has picked the correct type to match the column

-    // so getObject will return the appropriate type and the cast will succeed.

-    @SuppressWarnings("unchecked")

-    @Override

-    protected T handleRow(final ResultSet resultSet) throws SQLException {

-        if (this.columnName == null) {

-            return (T) resultSet.getObject(this.columnIndex);

-        }

-        return (T) resultSet.getObject(this.columnName);

-   }

-

-}

+/*
+ * 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.dbutils.handlers;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * {@code ResultSetHandler} implementation that converts one
+ * {@code ResultSet} column into a {@code List} of
+ * {@code Object}s. This class is thread safe.
+ *
+ * @param <T> The type of the column.
+ * @see org.apache.commons.dbutils.ResultSetHandler
+ * @since 1.1
+ */
+public class ColumnListHandler<T> extends AbstractListHandler<T> {
+
+    /**
+     * The column number to retrieve.
+     */
+    private final int columnIndex;
+
+    /**
+     * The column name to retrieve.  Either columnName or columnIndex
+     * will be used but never both.
+     */
+    private final String columnName;
+
+    /**
+     * Creates a new instance of ColumnListHandler.  The first column of each
+     * row will be returned from {@code handle()}.
+     */
+    public ColumnListHandler() {
+        this(1, null);
+    }
+
+    /**
+     * Creates a new instance of ColumnListHandler.
+     *
+     * @param columnIndex The index of the column to retrieve from the
+     * {@code ResultSet}.
+     */
+    public ColumnListHandler(final int columnIndex) {
+        this(columnIndex, null);
+    }
+
+    /** Private Helper
+     * @param columnIndex The index of the column to retrieve from the
+     * {@code ResultSet}.
+     * @param columnName The name of the column to retrieve from the
+     * {@code ResultSet}.
+     */
+    private ColumnListHandler(final int columnIndex, final String columnName) {
+        this.columnIndex = columnIndex;
+        this.columnName = columnName;
+    }
+
+    /**
+     * Creates a new instance of ColumnListHandler.
+     *
+     * @param columnName The name of the column to retrieve from the
+     * {@code ResultSet}.
+     */
+    public ColumnListHandler(final String columnName) {
+        this(1, columnName);
+    }
+
+    /**
+     * Returns one {@code ResultSet} column value as {@code Object}.
+     * @param resultSet {@code ResultSet} to process.
+     * @return {@code Object}, never {@code null}.
+     * @throws SQLException if a database access error occurs
+     * @throws ClassCastException if the class datatype does not match the column type
+     * @see org.apache.commons.dbutils.handlers.AbstractListHandler#handle(ResultSet)
+     */
+    // We assume that the user has picked the correct type to match the column
+    // so getObject will return the appropriate type and the cast will succeed.
+    @SuppressWarnings("unchecked")
+    @Override
+    protected T handleRow(final ResultSet resultSet) throws SQLException {
+        if (this.columnName == null) {
+            return (T) resultSet.getObject(this.columnIndex);
+        }
+        return (T) resultSet.getObject(this.columnName);
+   }
+
+}
diff --git a/src/main/java/org/apache/commons/dbutils/handlers/KeyedHandler.java b/src/main/java/org/apache/commons/dbutils/handlers/KeyedHandler.java
index 7ec1452..379ea81 100644
--- a/src/main/java/org/apache/commons/dbutils/handlers/KeyedHandler.java
+++ b/src/main/java/org/apache/commons/dbutils/handlers/KeyedHandler.java
@@ -1,159 +1,159 @@
-/*

- * 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.dbutils.handlers;

-

-import java.sql.ResultSet;

-import java.sql.SQLException;

-import java.util.Map;

-

-import org.apache.commons.dbutils.RowProcessor;

-

-/**

- * <p>

- * {@code ResultSetHandler} implementation that returns a Map of Maps.

- * {@code ResultSet} rows are converted into Maps which are then stored

- * in a Map under the given key.

- * </p>

- * <p>

- * If you had a Person table with a primary key column called ID, you could

- * retrieve rows from the table like this:

- * <pre>

- * ResultSetHandler h = new KeyedHandler("id");

- * Map found = (Map) queryRunner.query("select id, name, age from person", h);

- * Map jane = (Map) found.get(new Long(1)); // jane's id is 1

- * String janesName = (String) jane.get("name");

- * Integer janesAge = (Integer) jane.get("age");

- * </pre>

- * Note that the "id" passed to KeyedHandler and "name" and "age" passed to the

- * returned Map's get() method can be in any case.  The data types returned for

- * name and age are dependent upon how your JDBC driver converts SQL column

- * types from the Person table into Java types.

- * &lt;/p&gt;

- * <p>This class is thread safe.</p>

- *

- * @param <K> The type of the key

- * @see org.apache.commons.dbutils.ResultSetHandler

- * @since 1.1

- */

-public class KeyedHandler<K> extends AbstractKeyedHandler<K, Map<String, Object>> {

-

-    /**

-     * The RowProcessor implementation to use when converting rows

-     * into Objects.

-     */

-    protected final RowProcessor convert;

-

-    /**

-     * The column index to retrieve key values from.  Defaults to 1.

-     */

-    protected final int columnIndex;

-

-    /**

-     * The column name to retrieve key values from.  Either columnName or

-     * columnIndex will be used but never both.

-     */

-    protected final String columnName;

-

-    /**

-     * Creates a new instance of KeyedHandler.  The value of the first column

-     * of each row will be a key in the Map.

-     */

-    public KeyedHandler() {

-        this(ArrayHandler.ROW_PROCESSOR, 1, null);

-    }

-

-    /**

-     * Creates a new instance of KeyedHandler.

-     *

-     * @param columnIndex The values to use as keys in the Map are

-     * retrieved from the column at this index.

-     */

-    public KeyedHandler(final int columnIndex) {

-        this(ArrayHandler.ROW_PROCESSOR, columnIndex, null);

-    }

-

-    /**

-     * Creates a new instance of KeyedHandler.  The value of the first column

-     * of each row will be a key in the Map.

-     *

-     * @param convert The {@code RowProcessor} implementation

-     * to use when converting rows into Maps

-     */

-    public KeyedHandler(final RowProcessor convert) {

-        this(convert, 1, null);

-    }

-

-    /** Private Helper

-     * @param convert The {@code RowProcessor} implementation

-     * to use when converting rows into Maps

-     * @param columnIndex The values to use as keys in the Map are

-     * retrieved from the column at this index.

-     * @param columnName The values to use as keys in the Map are

-     * retrieved from the column with this name.

-     */

-    private KeyedHandler(final RowProcessor convert, final int columnIndex,

-            final String columnName) {

-        this.convert = convert;

-        this.columnIndex = columnIndex;

-        this.columnName = columnName;

-    }

-

-    /**

-     * Creates a new instance of KeyedHandler.

-     *

-     * @param columnName The values to use as keys in the Map are

-     * retrieved from the column with this name.

-     */

-    public KeyedHandler(final String columnName) {

-        this(ArrayHandler.ROW_PROCESSOR, 1, columnName);

-    }

-    /**

-     * This factory method is called by {@code handle()} to retrieve the

-     * key value from the current {@code ResultSet} row.  This

-     * implementation returns {@code ResultSet.getObject()} for the

-     * configured key column name or index.

-     * @param rs ResultSet to create a key from

-     * @return Object from the configured key column name/index

-     * @throws SQLException if a database access error occurs

-     * @throws ClassCastException if the class datatype does not match the column type

-     */

-    // We assume that the user has picked the correct type to match the column

-    // so getObject will return the appropriate type and the cast will succeed.

-    @SuppressWarnings("unchecked")

-    @Override

-    protected K createKey(final ResultSet rs) throws SQLException {

-        return columnName == null ?

-               (K) rs.getObject(columnIndex) :

-               (K) rs.getObject(columnName);

-    }

-

-    /**

-     * This factory method is called by {@code handle()} to store the

-     * current {@code ResultSet} row in some object. This

-     * implementation returns a {@code Map} with case insensitive column

-     * names as keys.  Calls to {@code map.get("COL")} and

-     * {@code map.get("col")} return the same value.

-     * @param resultSet ResultSet to create a row from

-     * @return Object typed Map containing column names to values

-     * @throws SQLException if a database access error occurs

-     */

-    @Override

-    protected Map<String, Object> createRow(final ResultSet resultSet) throws SQLException {

-        return this.convert.toMap(resultSet);

-    }

-

-}

+/*
+ * 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.dbutils.handlers;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Map;
+
+import org.apache.commons.dbutils.RowProcessor;
+
+/**
+ * <p>
+ * {@code ResultSetHandler} implementation that returns a Map of Maps.
+ * {@code ResultSet} rows are converted into Maps which are then stored
+ * in a Map under the given key.
+ * </p>
+ * <p>
+ * If you had a Person table with a primary key column called ID, you could
+ * retrieve rows from the table like this:
+ * <pre>
+ * ResultSetHandler h = new KeyedHandler("id");
+ * Map found = (Map) queryRunner.query("select id, name, age from person", h);
+ * Map jane = (Map) found.get(new Long(1)); // jane's id is 1
+ * String janesName = (String) jane.get("name");
+ * Integer janesAge = (Integer) jane.get("age");
+ * </pre>
+ * Note that the "id" passed to KeyedHandler and "name" and "age" passed to the
+ * returned Map's get() method can be in any case.  The data types returned for
+ * name and age are dependent upon how your JDBC driver converts SQL column
+ * types from the Person table into Java types.
+ * &lt;/p&gt;
+ * <p>This class is thread safe.</p>
+ *
+ * @param <K> The type of the key
+ * @see org.apache.commons.dbutils.ResultSetHandler
+ * @since 1.1
+ */
+public class KeyedHandler<K> extends AbstractKeyedHandler<K, Map<String, Object>> {
+
+    /**
+     * The RowProcessor implementation to use when converting rows
+     * into Objects.
+     */
+    protected final RowProcessor convert;
+
+    /**
+     * The column index to retrieve key values from.  Defaults to 1.
+     */
+    protected final int columnIndex;
+
+    /**
+     * The column name to retrieve key values from.  Either columnName or
+     * columnIndex will be used but never both.
+     */
+    protected final String columnName;
+
+    /**
+     * Creates a new instance of KeyedHandler.  The value of the first column
+     * of each row will be a key in the Map.
+     */
+    public KeyedHandler() {
+        this(ArrayHandler.ROW_PROCESSOR, 1, null);
+    }
+
+    /**
+     * Creates a new instance of KeyedHandler.
+     *
+     * @param columnIndex The values to use as keys in the Map are
+     * retrieved from the column at this index.
+     */
+    public KeyedHandler(final int columnIndex) {
+        this(ArrayHandler.ROW_PROCESSOR, columnIndex, null);
+    }
+
+    /**
+     * Creates a new instance of KeyedHandler.  The value of the first column
+     * of each row will be a key in the Map.
+     *
+     * @param convert The {@code RowProcessor} implementation
+     * to use when converting rows into Maps
+     */
+    public KeyedHandler(final RowProcessor convert) {
+        this(convert, 1, null);
+    }
+
+    /** Private Helper
+     * @param convert The {@code RowProcessor} implementation
+     * to use when converting rows into Maps
+     * @param columnIndex The values to use as keys in the Map are
+     * retrieved from the column at this index.
+     * @param columnName The values to use as keys in the Map are
+     * retrieved from the column with this name.
+     */
+    private KeyedHandler(final RowProcessor convert, final int columnIndex,
+            final String columnName) {
+        this.convert = convert;
+        this.columnIndex = columnIndex;
+        this.columnName = columnName;
+    }
+
+    /**
+     * Creates a new instance of KeyedHandler.
+     *
+     * @param columnName The values to use as keys in the Map are
+     * retrieved from the column with this name.
+     */
+    public KeyedHandler(final String columnName) {
+        this(ArrayHandler.ROW_PROCESSOR, 1, columnName);
+    }
+    /**
+     * This factory method is called by {@code handle()} to retrieve the
+     * key value from the current {@code ResultSet} row.  This
+     * implementation returns {@code ResultSet.getObject()} for the
+     * configured key column name or index.
+     * @param rs ResultSet to create a key from
+     * @return Object from the configured key column name/index
+     * @throws SQLException if a database access error occurs
+     * @throws ClassCastException if the class datatype does not match the column type
+     */
+    // We assume that the user has picked the correct type to match the column
+    // so getObject will return the appropriate type and the cast will succeed.
+    @SuppressWarnings("unchecked")
+    @Override
+    protected K createKey(final ResultSet rs) throws SQLException {
+        return columnName == null ?
+               (K) rs.getObject(columnIndex) :
+               (K) rs.getObject(columnName);
+    }
+
+    /**
+     * This factory method is called by {@code handle()} to store the
+     * current {@code ResultSet} row in some object. This
+     * implementation returns a {@code Map} with case insensitive column
+     * names as keys.  Calls to {@code map.get("COL")} and
+     * {@code map.get("col")} return the same value.
+     * @param resultSet ResultSet to create a row from
+     * @return Object typed Map containing column names to values
+     * @throws SQLException if a database access error occurs
+     */
+    @Override
+    protected Map<String, Object> createRow(final ResultSet resultSet) throws SQLException {
+        return this.convert.toMap(resultSet);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/dbutils/wrappers/SqlNullCheckedResultSet.java b/src/main/java/org/apache/commons/dbutils/wrappers/SqlNullCheckedResultSet.java
index da342e5..2726c74 100644
--- a/src/main/java/org/apache/commons/dbutils/wrappers/SqlNullCheckedResultSet.java
+++ b/src/main/java/org/apache/commons/dbutils/wrappers/SqlNullCheckedResultSet.java
@@ -1,618 +1,618 @@
-/*

- * 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.dbutils.wrappers;

-

-import java.io.InputStream;

-import java.io.Reader;

-import java.lang.reflect.InvocationHandler;

-import java.lang.reflect.Method;

-import java.math.BigDecimal;

-import java.net.URL;

-import java.sql.Blob;

-import java.sql.Clob;

-import java.sql.Date;

-import java.sql.Ref;

-import java.sql.ResultSet;

-import java.sql.Time;

-import java.sql.Timestamp;

-import java.util.HashMap;

-import java.util.Map;

-

-import org.apache.commons.dbutils.ProxyFactory;

-

-/**

- * Decorates a {@code ResultSet} with checks for a SQL NULL value on each

- * {@code getXXX} method. If a column value obtained by a

- * {@code getXXX} method is not SQL NULL, the column value is returned. If

- * the column value is SQL null, an alternate value is returned. The alternate

- * value defaults to the Java {@code null} value, which can be overridden

- * for instances of the class.

- *

- * <p>

- * Usage example:

- * <blockquote>

- * <pre>

- * Connection conn = // somehow get a connection

- * Statement stmt = conn.createStatement();

- * ResultSet resultSet = stmt.executeQuery("SELECT col1, col2 FROM table1");

- *

- * // Wrap the result set for SQL NULL checking

- * SqlNullCheckedResultSet wrapper = new SqlNullCheckedResultSet(resultSet);

- * wrapper.setNullString("---N/A---"); // Set null string

- * wrapper.setNullInt(-999); // Set null integer

- * resultSet = ProxyFactory.instance().createResultSet(wrapper);

- *

- * while (resultSet.next()) {

- *     // If col1 is SQL NULL, value returned will be "---N/A---"

- *     String col1 = resultSet.getString("col1");

- *     // If col2 is SQL NULL, value returned will be -999

- *     int col2 = resultSet.getInt("col2");

- * }

- * resultSet.close();

- * </pre>

- * </blockquote>

- * &lt;/p&gt;

- * <p>Unlike some other classes in DbUtils, this class is NOT thread-safe.</p>

- */

-public class SqlNullCheckedResultSet implements InvocationHandler {

-

-    /**

-     * Maps normal method names (ie. "getBigDecimal") to the corresponding null

-     * Method object (ie. getNullBigDecimal).

-     */

-    private static final Map<String, Method> NULL_METHODS = new HashMap<>();

-

-    /**

-     * The {@code getNull} string prefix.

-     * @since 1.4

-     */

-    private static final String GET_NULL_PREFIX = "getNull";

-

-    static {

-        final Method[] methods = SqlNullCheckedResultSet.class.getMethods();

-        for (final Method method : methods) {

-            final String methodName = method.getName();

-

-            if (methodName.startsWith(GET_NULL_PREFIX)) {

-                final String normalName = "get" + methodName.substring(GET_NULL_PREFIX.length());

-                NULL_METHODS.put(normalName, method);

-            }

-        }

-    }

-

-    /**

-     * The factory to create proxies with.

-     */

-    private static final ProxyFactory factory = ProxyFactory.instance();

-

-    /**

-     * Wraps the {@code ResultSet} in an instance of this class.  This is

-     * equivalent to:

-     * <pre>

-     * ProxyFactory.instance().createResultSet(new SqlNullCheckedResultSet(resultSet));

-     * </pre>

-     *

-     * @param resultSet The {@code ResultSet} to wrap.

-     * @return wrapped ResultSet

-     */

-    public static ResultSet wrap(final ResultSet resultSet) {

-        return factory.createResultSet(new SqlNullCheckedResultSet(resultSet));

-    }

-

-    private InputStream nullAsciiStream;

-    private BigDecimal nullBigDecimal;

-    private InputStream nullBinaryStream;

-    private Blob nullBlob;

-    private boolean nullBoolean;

-    private byte nullByte;

-    private byte[] nullBytes;

-    private Reader nullCharacterStream;

-    private Clob nullClob;

-    private Date nullDate;

-    private double nullDouble;

-    private float nullFloat;

-    private int nullInt;

-    private long nullLong;

-    private Object nullObject;

-    private Ref nullRef;

-    private short nullShort;

-    private String nullString;

-    private Time nullTime;

-    private Timestamp nullTimestamp;

-    private URL nullURL;

-

-    /**

-     * The wrapped result.

-     */

-    private final ResultSet resultSet;

-

-    /**

-     * Constructs a new instance of

-     * {@code SqlNullCheckedResultSet}

-     * to wrap the specified {@code ResultSet}.

-     * @param resultSet ResultSet to wrap

-     */

-    public SqlNullCheckedResultSet(final ResultSet resultSet) {

-        this.resultSet = resultSet;

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getAsciiStream} method.

-     *

-     * @return the value

-     */

-    public InputStream getNullAsciiStream() {

-        return this.nullAsciiStream;

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getBigDecimal} method.

-     *

-     * @return the value

-     */

-    public BigDecimal getNullBigDecimal() {

-        return this.nullBigDecimal;

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getBinaryStream} method.

-     *

-     * @return the value

-     */

-    public InputStream getNullBinaryStream() {

-        return this.nullBinaryStream;

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getBlob} method.

-     *

-     * @return the value

-     */

-    public Blob getNullBlob() {

-        return this.nullBlob;

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getBoolean} method.

-     *

-     * @return the value

-     */

-    public boolean getNullBoolean() {

-        return this.nullBoolean;

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getByte} method.

-     *

-     * @return the value

-     */

-    public byte getNullByte() {

-        return this.nullByte;

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getBytes} method.

-     *

-     * @return the value

-     */

-    public byte[] getNullBytes() {

-        if (this.nullBytes == null) {

-            return null;

-        }

-        return this.nullBytes.clone();

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getCharacterStream} method.

-     *

-     * @return the value

-     */

-    public Reader getNullCharacterStream() {

-        return this.nullCharacterStream;

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getClob} method.

-     *

-     * @return the value

-     */

-    public Clob getNullClob() {

-        return this.nullClob;

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getDate} method.

-     *

-     * @return the value

-     */

-    public Date getNullDate() {

-        return this.nullDate != null ? new Date(this.nullDate.getTime()) : null;

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getDouble} method.

-     *

-     * @return the value

-     */

-    public double getNullDouble() {

-        return this.nullDouble;

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getFloat} method.

-     *

-     * @return the value

-     */

-    public float getNullFloat() {

-        return this.nullFloat;

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getInt} method.

-     *

-     * @return the value

-     */

-    public int getNullInt() {

-        return this.nullInt;

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getLong} method.

-     *

-     * @return the value

-     */

-    public long getNullLong() {

-        return this.nullLong;

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getObject} method.

-     *

-     * @return the value

-     */

-    public Object getNullObject() {

-        return this.nullObject;

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getRef} method.

-     *

-     * @return the value

-     */

-    public Ref getNullRef() {

-        return this.nullRef;

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getShort} method.

-     *

-     * @return the value

-     */

-    public short getNullShort() {

-        return this.nullShort;

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getString} method.

-     *

-     * @return the value

-     */

-    public String getNullString() {

-        return this.nullString;

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getTime} method.

-     *

-     * @return the value

-     */

-    public Time getNullTime() {

-        return this.nullTime != null ? new Time(this.nullTime.getTime()) : null;

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getTimestamp} method.

-     *

-     * @return the value

-     */

-    public Timestamp getNullTimestamp() {

-        if (this.nullTimestamp == null) {

-            return null;

-        }

-

-        final Timestamp ts = new Timestamp(this.nullTimestamp.getTime());

-        ts.setNanos(this.nullTimestamp.getNanos());

-        return ts;

-    }

-

-    /**

-     * Returns the value when a SQL null is encountered as the result of

-     * invoking a {@code getURL} method.

-     *

-     * @return the value

-     */

-    public URL getNullURL() {

-        return this.nullURL;

-    }

-

-    /**

-     * Intercepts calls to {@code get*} methods and calls the appropriate

-     * {@code getNull*} method if the {@code ResultSet} returned

-     * {@code null}.

-     *

-     * @see java.lang.reflect.InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])

-     * @param proxy Not used; all method calls go to the internal result set

-     * @param method The method to invoke on the result set

-     * @param args The arguments to pass to the result set

-     * @return null checked result

-     * @throws Throwable error

-     */

-    @Override

-    public Object invoke(final Object proxy, final Method method, final Object[] args)

-        throws Throwable {

-

-        final Object result = method.invoke(this.resultSet, args);

-

-        final Method nullMethod = NULL_METHODS.get(method.getName());

-

-        // Check nullMethod != null first so that we don't call wasNull()

-        // before a true getter method was invoked on the ResultSet.

-        return nullMethod != null && this.resultSet.wasNull()

-            ? nullMethod.invoke(this, (Object[]) null)

-            : result;

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getAsciiStream} method.

-     *

-     * @param nullAsciiStream the value

-     */

-    public void setNullAsciiStream(final InputStream nullAsciiStream) {

-        this.nullAsciiStream = nullAsciiStream;

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getBigDecimal} method.

-     *

-     * @param nullBigDecimal the value

-     */

-    public void setNullBigDecimal(final BigDecimal nullBigDecimal) {

-        this.nullBigDecimal = nullBigDecimal;

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getBinaryStream} method.

-     *

-     * @param nullBinaryStream the value

-     */

-    public void setNullBinaryStream(final InputStream nullBinaryStream) {

-        this.nullBinaryStream = nullBinaryStream;

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getBlob} method.

-     *

-     * @param nullBlob the value

-     */

-    public void setNullBlob(final Blob nullBlob) {

-        this.nullBlob = nullBlob;

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getBoolean} method.

-     *

-     * @param nullBoolean the value

-     */

-    public void setNullBoolean(final boolean nullBoolean) {

-        this.nullBoolean = nullBoolean;

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getByte} method.

-     *

-     * @param nullByte the value

-     */

-    public void setNullByte(final byte nullByte) {

-        this.nullByte = nullByte;

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getBytes} method.

-     *

-     * @param nullBytes the value

-     */

-    public void setNullBytes(final byte[] nullBytes) {

-        if (nullBytes != null) {

-            this.nullBytes = nullBytes.clone();

-        } else {

-            this.nullBytes = null;

-        }

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getCharacterStream} method.

-     *

-     * @param nullCharacterStream the value

-     */

-    public void setNullCharacterStream(final Reader nullCharacterStream) {

-        this.nullCharacterStream = nullCharacterStream;

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getClob} method.

-     *

-     * @param nullClob the value

-     */

-    public void setNullClob(final Clob nullClob) {

-        this.nullClob = nullClob;

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getDate} method.

-     *

-     * @param nullDate the value

-     */

-    public void setNullDate(final Date nullDate) {

-        this.nullDate = nullDate != null ? new Date(nullDate.getTime()) : null;

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getDouble} method.

-     *

-     * @param nullDouble the value

-     */

-    public void setNullDouble(final double nullDouble) {

-        this.nullDouble = nullDouble;

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getFloat} method.

-     *

-     * @param nullFloat the value

-     */

-    public void setNullFloat(final float nullFloat) {

-        this.nullFloat = nullFloat;

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getInt} method.

-     *

-     * @param nullInt the value

-     */

-    public void setNullInt(final int nullInt) {

-        this.nullInt = nullInt;

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getLong} method.

-     *

-     * @param nullLong the value

-     */

-    public void setNullLong(final long nullLong) {

-        this.nullLong = nullLong;

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getObject} method.

-     *

-     * @param nullObject the value

-     */

-    public void setNullObject(final Object nullObject) {

-        this.nullObject = nullObject;

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getRef} method.

-     *

-     * @param nullRef the value

-     */

-    public void setNullRef(final Ref nullRef) {

-        this.nullRef = nullRef;

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getShort} method.

-     *

-     * @param nullShort the value

-     */

-    public void setNullShort(final short nullShort) {

-        this.nullShort = nullShort;

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getString} method.

-     *

-     * @param nullString the value

-     */

-    public void setNullString(final String nullString) {

-        this.nullString = nullString;

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getTime} method.

-     *

-     * @param nullTime the value

-     */

-    public void setNullTime(final Time nullTime) {

-        this.nullTime = nullTime != null ? new Time(nullTime.getTime()) : null;

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getTimestamp} method.

-     *

-     * @param nullTimestamp the value

-     */

-    public void setNullTimestamp(final Timestamp nullTimestamp) {

-        if (nullTimestamp != null) {

-            this.nullTimestamp = new Timestamp(nullTimestamp.getTime());

-            this.nullTimestamp.setNanos(nullTimestamp.getNanos());

-        } else {

-            this.nullTimestamp = null;

-        }

-    }

-

-    /**

-     * Sets the value to return when a SQL null is encountered as the result of

-     * invoking a {@code getURL} method.

-     *

-     * @param nullURL the value

-     */

-    public void setNullURL(final URL nullURL) {

-        this.nullURL = nullURL;

-    }

-

-}

+/*
+ * 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.dbutils.wrappers;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Date;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.dbutils.ProxyFactory;
+
+/**
+ * Decorates a {@code ResultSet} with checks for a SQL NULL value on each
+ * {@code getXXX} method. If a column value obtained by a
+ * {@code getXXX} method is not SQL NULL, the column value is returned. If
+ * the column value is SQL null, an alternate value is returned. The alternate
+ * value defaults to the Java {@code null} value, which can be overridden
+ * for instances of the class.
+ *
+ * <p>
+ * Usage example:
+ * <blockquote>
+ * <pre>
+ * Connection conn = // somehow get a connection
+ * Statement stmt = conn.createStatement();
+ * ResultSet resultSet = stmt.executeQuery("SELECT col1, col2 FROM table1");
+ *
+ * // Wrap the result set for SQL NULL checking
+ * SqlNullCheckedResultSet wrapper = new SqlNullCheckedResultSet(resultSet);
+ * wrapper.setNullString("---N/A---"); // Set null string
+ * wrapper.setNullInt(-999); // Set null integer
+ * resultSet = ProxyFactory.instance().createResultSet(wrapper);
+ *
+ * while (resultSet.next()) {
+ *     // If col1 is SQL NULL, value returned will be "---N/A---"
+ *     String col1 = resultSet.getString("col1");
+ *     // If col2 is SQL NULL, value returned will be -999
+ *     int col2 = resultSet.getInt("col2");
+ * }
+ * resultSet.close();
+ * </pre>
+ * </blockquote>
+ * &lt;/p&gt;
+ * <p>Unlike some other classes in DbUtils, this class is NOT thread-safe.</p>
+ */
+public class SqlNullCheckedResultSet implements InvocationHandler {
+
+    /**
+     * Maps normal method names (ie. "getBigDecimal") to the corresponding null
+     * Method object (ie. getNullBigDecimal).
+     */
+    private static final Map<String, Method> NULL_METHODS = new HashMap<>();
+
+    /**
+     * The {@code getNull} string prefix.
+     * @since 1.4
+     */
+    private static final String GET_NULL_PREFIX = "getNull";
+
+    static {
+        final Method[] methods = SqlNullCheckedResultSet.class.getMethods();
+        for (final Method method : methods) {
+            final String methodName = method.getName();
+
+            if (methodName.startsWith(GET_NULL_PREFIX)) {
+                final String normalName = "get" + methodName.substring(GET_NULL_PREFIX.length());
+                NULL_METHODS.put(normalName, method);
+            }
+        }
+    }
+
+    /**
+     * The factory to create proxies with.
+     */
+    private static final ProxyFactory factory = ProxyFactory.instance();
+
+    /**
+     * Wraps the {@code ResultSet} in an instance of this class.  This is
+     * equivalent to:
+     * <pre>
+     * ProxyFactory.instance().createResultSet(new SqlNullCheckedResultSet(resultSet));
+     * </pre>
+     *
+     * @param resultSet The {@code ResultSet} to wrap.
+     * @return wrapped ResultSet
+     */
+    public static ResultSet wrap(final ResultSet resultSet) {
+        return factory.createResultSet(new SqlNullCheckedResultSet(resultSet));
+    }
+
+    private InputStream nullAsciiStream;
+    private BigDecimal nullBigDecimal;
+    private InputStream nullBinaryStream;
+    private Blob nullBlob;
+    private boolean nullBoolean;
+    private byte nullByte;
+    private byte[] nullBytes;
+    private Reader nullCharacterStream;
+    private Clob nullClob;
+    private Date nullDate;
+    private double nullDouble;
+    private float nullFloat;
+    private int nullInt;
+    private long nullLong;
+    private Object nullObject;
+    private Ref nullRef;
+    private short nullShort;
+    private String nullString;
+    private Time nullTime;
+    private Timestamp nullTimestamp;
+    private URL nullURL;
+
+    /**
+     * The wrapped result.
+     */
+    private final ResultSet resultSet;
+
+    /**
+     * Constructs a new instance of
+     * {@code SqlNullCheckedResultSet}
+     * to wrap the specified {@code ResultSet}.
+     * @param resultSet ResultSet to wrap
+     */
+    public SqlNullCheckedResultSet(final ResultSet resultSet) {
+        this.resultSet = resultSet;
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getAsciiStream} method.
+     *
+     * @return the value
+     */
+    public InputStream getNullAsciiStream() {
+        return this.nullAsciiStream;
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getBigDecimal} method.
+     *
+     * @return the value
+     */
+    public BigDecimal getNullBigDecimal() {
+        return this.nullBigDecimal;
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getBinaryStream} method.
+     *
+     * @return the value
+     */
+    public InputStream getNullBinaryStream() {
+        return this.nullBinaryStream;
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getBlob} method.
+     *
+     * @return the value
+     */
+    public Blob getNullBlob() {
+        return this.nullBlob;
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getBoolean} method.
+     *
+     * @return the value
+     */
+    public boolean getNullBoolean() {
+        return this.nullBoolean;
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getByte} method.
+     *
+     * @return the value
+     */
+    public byte getNullByte() {
+        return this.nullByte;
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getBytes} method.
+     *
+     * @return the value
+     */
+    public byte[] getNullBytes() {
+        if (this.nullBytes == null) {
+            return null;
+        }
+        return this.nullBytes.clone();
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getCharacterStream} method.
+     *
+     * @return the value
+     */
+    public Reader getNullCharacterStream() {
+        return this.nullCharacterStream;
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getClob} method.
+     *
+     * @return the value
+     */
+    public Clob getNullClob() {
+        return this.nullClob;
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getDate} method.
+     *
+     * @return the value
+     */
+    public Date getNullDate() {
+        return this.nullDate != null ? new Date(this.nullDate.getTime()) : null;
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getDouble} method.
+     *
+     * @return the value
+     */
+    public double getNullDouble() {
+        return this.nullDouble;
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getFloat} method.
+     *
+     * @return the value
+     */
+    public float getNullFloat() {
+        return this.nullFloat;
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getInt} method.
+     *
+     * @return the value
+     */
+    public int getNullInt() {
+        return this.nullInt;
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getLong} method.
+     *
+     * @return the value
+     */
+    public long getNullLong() {
+        return this.nullLong;
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getObject} method.
+     *
+     * @return the value
+     */
+    public Object getNullObject() {
+        return this.nullObject;
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getRef} method.
+     *
+     * @return the value
+     */
+    public Ref getNullRef() {
+        return this.nullRef;
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getShort} method.
+     *
+     * @return the value
+     */
+    public short getNullShort() {
+        return this.nullShort;
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getString} method.
+     *
+     * @return the value
+     */
+    public String getNullString() {
+        return this.nullString;
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getTime} method.
+     *
+     * @return the value
+     */
+    public Time getNullTime() {
+        return this.nullTime != null ? new Time(this.nullTime.getTime()) : null;
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getTimestamp} method.
+     *
+     * @return the value
+     */
+    public Timestamp getNullTimestamp() {
+        if (this.nullTimestamp == null) {
+            return null;
+        }
+
+        final Timestamp ts = new Timestamp(this.nullTimestamp.getTime());
+        ts.setNanos(this.nullTimestamp.getNanos());
+        return ts;
+    }
+
+    /**
+     * Returns the value when a SQL null is encountered as the result of
+     * invoking a {@code getURL} method.
+     *
+     * @return the value
+     */
+    public URL getNullURL() {
+        return this.nullURL;
+    }
+
+    /**
+     * Intercepts calls to {@code get*} methods and calls the appropriate
+     * {@code getNull*} method if the {@code ResultSet} returned
+     * {@code null}.
+     *
+     * @see java.lang.reflect.InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])
+     * @param proxy Not used; all method calls go to the internal result set
+     * @param method The method to invoke on the result set
+     * @param args The arguments to pass to the result set
+     * @return null checked result
+     * @throws Throwable error
+     */
+    @Override
+    public Object invoke(final Object proxy, final Method method, final Object[] args)
+        throws Throwable {
+
+        final Object result = method.invoke(this.resultSet, args);
+
+        final Method nullMethod = NULL_METHODS.get(method.getName());
+
+        // Check nullMethod != null first so that we don't call wasNull()
+        // before a true getter method was invoked on the ResultSet.
+        return nullMethod != null && this.resultSet.wasNull()
+            ? nullMethod.invoke(this, (Object[]) null)
+            : result;
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getAsciiStream} method.
+     *
+     * @param nullAsciiStream the value
+     */
+    public void setNullAsciiStream(final InputStream nullAsciiStream) {
+        this.nullAsciiStream = nullAsciiStream;
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getBigDecimal} method.
+     *
+     * @param nullBigDecimal the value
+     */
+    public void setNullBigDecimal(final BigDecimal nullBigDecimal) {
+        this.nullBigDecimal = nullBigDecimal;
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getBinaryStream} method.
+     *
+     * @param nullBinaryStream the value
+     */
+    public void setNullBinaryStream(final InputStream nullBinaryStream) {
+        this.nullBinaryStream = nullBinaryStream;
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getBlob} method.
+     *
+     * @param nullBlob the value
+     */
+    public void setNullBlob(final Blob nullBlob) {
+        this.nullBlob = nullBlob;
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getBoolean} method.
+     *
+     * @param nullBoolean the value
+     */
+    public void setNullBoolean(final boolean nullBoolean) {
+        this.nullBoolean = nullBoolean;
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getByte} method.
+     *
+     * @param nullByte the value
+     */
+    public void setNullByte(final byte nullByte) {
+        this.nullByte = nullByte;
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getBytes} method.
+     *
+     * @param nullBytes the value
+     */
+    public void setNullBytes(final byte[] nullBytes) {
+        if (nullBytes != null) {
+            this.nullBytes = nullBytes.clone();
+        } else {
+            this.nullBytes = null;
+        }
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getCharacterStream} method.
+     *
+     * @param nullCharacterStream the value
+     */
+    public void setNullCharacterStream(final Reader nullCharacterStream) {
+        this.nullCharacterStream = nullCharacterStream;
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getClob} method.
+     *
+     * @param nullClob the value
+     */
+    public void setNullClob(final Clob nullClob) {
+        this.nullClob = nullClob;
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getDate} method.
+     *
+     * @param nullDate the value
+     */
+    public void setNullDate(final Date nullDate) {
+        this.nullDate = nullDate != null ? new Date(nullDate.getTime()) : null;
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getDouble} method.
+     *
+     * @param nullDouble the value
+     */
+    public void setNullDouble(final double nullDouble) {
+        this.nullDouble = nullDouble;
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getFloat} method.
+     *
+     * @param nullFloat the value
+     */
+    public void setNullFloat(final float nullFloat) {
+        this.nullFloat = nullFloat;
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getInt} method.
+     *
+     * @param nullInt the value
+     */
+    public void setNullInt(final int nullInt) {
+        this.nullInt = nullInt;
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getLong} method.
+     *
+     * @param nullLong the value
+     */
+    public void setNullLong(final long nullLong) {
+        this.nullLong = nullLong;
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getObject} method.
+     *
+     * @param nullObject the value
+     */
+    public void setNullObject(final Object nullObject) {
+        this.nullObject = nullObject;
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getRef} method.
+     *
+     * @param nullRef the value
+     */
+    public void setNullRef(final Ref nullRef) {
+        this.nullRef = nullRef;
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getShort} method.
+     *
+     * @param nullShort the value
+     */
+    public void setNullShort(final short nullShort) {
+        this.nullShort = nullShort;
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getString} method.
+     *
+     * @param nullString the value
+     */
+    public void setNullString(final String nullString) {
+        this.nullString = nullString;
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getTime} method.
+     *
+     * @param nullTime the value
+     */
+    public void setNullTime(final Time nullTime) {
+        this.nullTime = nullTime != null ? new Time(nullTime.getTime()) : null;
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getTimestamp} method.
+     *
+     * @param nullTimestamp the value
+     */
+    public void setNullTimestamp(final Timestamp nullTimestamp) {
+        if (nullTimestamp != null) {
+            this.nullTimestamp = new Timestamp(nullTimestamp.getTime());
+            this.nullTimestamp.setNanos(nullTimestamp.getNanos());
+        } else {
+            this.nullTimestamp = null;
+        }
+    }
+
+    /**
+     * Sets the value to return when a SQL null is encountered as the result of
+     * invoking a {@code getURL} method.
+     *
+     * @param nullURL the value
+     */
+    public void setNullURL(final URL nullURL) {
+        this.nullURL = nullURL;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/dbutils/wrappers/StringTrimmedResultSet.java b/src/main/java/org/apache/commons/dbutils/wrappers/StringTrimmedResultSet.java
index da87f2c..532938e 100644
--- a/src/main/java/org/apache/commons/dbutils/wrappers/StringTrimmedResultSet.java
+++ b/src/main/java/org/apache/commons/dbutils/wrappers/StringTrimmedResultSet.java
@@ -1,103 +1,103 @@
-/*

- * 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.dbutils.wrappers;

-

-import java.lang.reflect.InvocationHandler;

-import java.lang.reflect.Method;

-import java.sql.ResultSet;

-

-import org.apache.commons.dbutils.ProxyFactory;

-

-/**

- * Wraps a {@code ResultSet} to trim strings returned by the

- * {@code getString()} and {@code getObject()} methods.

- *

- * <p>

- * Usage Example:

- * This example shows how to decorate ResultSets so processing continues as

- * normal but all Strings are trimmed before being returned from the

- * {@code ResultSet}.

- * </p>

- *

- * <pre>

- * ResultSet resultSet = // somehow get a ResultSet;

- *

- * // Substitute wrapped ResultSet with additional behavior for real ResultSet

- * resultSet = StringTrimmedResultSet.wrap(resultSet);

- *

- * // Pass wrapped ResultSet to processor

- * List list = new BasicRowProcessor().toBeanList(resultSet);

- * </pre>

- */

-public class StringTrimmedResultSet implements InvocationHandler {

-

-    /**

-     * Wraps the {@code ResultSet} in an instance of this class.  This is

-     * equivalent to:

-     * <pre>

-     * ProxyFactory.instance().createResultSet(new StringTrimmedResultSet(resultSet));

-     * </pre>

-     *

-     * @param resultSet The {@code ResultSet} to wrap.

-     * @return wrapped ResultSet

-     */

-    public static ResultSet wrap(final ResultSet resultSet) {

-        return ProxyFactory.instance().createResultSet(new StringTrimmedResultSet(resultSet));

-    }

-

-    /**

-     * The wrapped result.

-     */

-    private final ResultSet resultSet;

-

-    /**

-     * Constructs a new instance of {@code StringTrimmedResultSet}

-     * to wrap the specified {@code ResultSet}.

-     * @param resultSet ResultSet to wrap

-     */

-    public StringTrimmedResultSet(final ResultSet resultSet) {

-        this.resultSet = resultSet;

-    }

-

-    /**

-     * Intercept calls to the {@code getString()} and

-     * {@code getObject()} methods and trim any Strings before they're

-     * returned.

-     *

-     * @see java.lang.reflect.InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])

-     * @param proxy Not used; all method calls go to the internal result set

-     * @param method The method to invoke on the result set

-     * @param args The arguments to pass to the result set

-     * @return string trimmed result

-     * @throws Throwable error

-     */

-    @Override

-    public Object invoke(final Object proxy, final Method method, final Object[] args)

-        throws Throwable {

-

-        Object result = method.invoke(this.resultSet, args);

-

-        if (result instanceof String

-                && (method.getName().equals("getObject")

-                || method.getName().equals("getString"))) {

-            result = ((String) result).trim();

-        }

-

-        return result;

-    }

-

-}

+/*
+ * 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.dbutils.wrappers;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.sql.ResultSet;
+
+import org.apache.commons.dbutils.ProxyFactory;
+
+/**
+ * Wraps a {@code ResultSet} to trim strings returned by the
+ * {@code getString()} and {@code getObject()} methods.
+ *
+ * <p>
+ * Usage Example:
+ * This example shows how to decorate ResultSets so processing continues as
+ * normal but all Strings are trimmed before being returned from the
+ * {@code ResultSet}.
+ * </p>
+ *
+ * <pre>
+ * ResultSet resultSet = // somehow get a ResultSet;
+ *
+ * // Substitute wrapped ResultSet with additional behavior for real ResultSet
+ * resultSet = StringTrimmedResultSet.wrap(resultSet);
+ *
+ * // Pass wrapped ResultSet to processor
+ * List list = new BasicRowProcessor().toBeanList(resultSet);
+ * </pre>
+ */
+public class StringTrimmedResultSet implements InvocationHandler {
+
+    /**
+     * Wraps the {@code ResultSet} in an instance of this class.  This is
+     * equivalent to:
+     * <pre>
+     * ProxyFactory.instance().createResultSet(new StringTrimmedResultSet(resultSet));
+     * </pre>
+     *
+     * @param resultSet The {@code ResultSet} to wrap.
+     * @return wrapped ResultSet
+     */
+    public static ResultSet wrap(final ResultSet resultSet) {
+        return ProxyFactory.instance().createResultSet(new StringTrimmedResultSet(resultSet));
+    }
+
+    /**
+     * The wrapped result.
+     */
+    private final ResultSet resultSet;
+
+    /**
+     * Constructs a new instance of {@code StringTrimmedResultSet}
+     * to wrap the specified {@code ResultSet}.
+     * @param resultSet ResultSet to wrap
+     */
+    public StringTrimmedResultSet(final ResultSet resultSet) {
+        this.resultSet = resultSet;
+    }
+
+    /**
+     * Intercept calls to the {@code getString()} and
+     * {@code getObject()} methods and trim any Strings before they're
+     * returned.
+     *
+     * @see java.lang.reflect.InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])
+     * @param proxy Not used; all method calls go to the internal result set
+     * @param method The method to invoke on the result set
+     * @param args The arguments to pass to the result set
+     * @return string trimmed result
+     * @throws Throwable error
+     */
+    @Override
+    public Object invoke(final Object proxy, final Method method, final Object[] args)
+        throws Throwable {
+
+        Object result = method.invoke(this.resultSet, args);
+
+        if (result instanceof String
+                && (method.getName().equals("getObject")
+                || method.getName().equals("getString"))) {
+            result = ((String) result).trim();
+        }
+
+        return result;
+    }
+
+}
diff --git a/src/site/resources/spotbugs/sb-excludes.xml b/src/site/resources/spotbugs/sb-excludes.xml
index f4a2442..48dd458 100644
--- a/src/site/resources/spotbugs/sb-excludes.xml
+++ b/src/site/resources/spotbugs/sb-excludes.xml
@@ -1,69 +1,69 @@
-<?xml version="1.0" encoding="UTF-8"?>

-<!--

-   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.

--->

-<FindBugsFilter

-    xmlns="https://github.com/spotbugs/filter/3.0.0"

-    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

-    xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd">

-

-    <!-- TODO Can any of these be done without breaking binary compatibility? -->

-    <Match>

-        <Or>

-            <Bug pattern="EI_EXPOSE_REP" />

-            <Bug pattern="EI_EXPOSE_REP2" />

-        </Or>

-    </Match>

-    <!-- PrintWriter wraps System.err which always uses the default encoding -->

-    <Match>

-        <Class name="org.apache.commons.dbutils.DbUtils" />

-        <Method name="printStackTrace" />

-        <Bug pattern="DM_DEFAULT_ENCODING" />

-    </Match>

-    <!-- PrintWriter wraps System.err which always uses the default encoding -->

-    <Match>

-        <Class name="org.apache.commons.dbutils.DbUtils" />

-        <Method name="printWarnings" />

-        <Bug pattern="DM_DEFAULT_ENCODING" />

-    </Match>

-    <!-- The Javadocs state and the called code shows that null can be returned. -->

-    <Match>

-        <Class name="org.apache.commons.dbutils.QueryLoader" />

-        <Method name="loadQueries" />

-        <Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE" />

-    </Match>

-    <!-- This method returns a boolean indicating status. -->

-    <Match>

-        <Class name="org.apache.commons.dbutils.DbUtils" />

-        <Method name="loadDriver" />

-        <Bug pattern="REC_CATCH_EXCEPTION" />

-    </Match>

-    <!-- Compatibility -->

-    <Match>

-        <Class name="org.apache.commons.dbutils.BasicRowProcessor" />

-        <Bug pattern="SING_SINGLETON_HAS_NONPRIVATE_CONSTRUCTOR" />

-    </Match>

-    <!-- Compatibility -->

-    <Match>

-        <Class name="org.apache.commons.dbutils.ProxyFactory" />

-        <Bug pattern="SING_SINGLETON_HAS_NONPRIVATE_CONSTRUCTOR" />

-    </Match>

-    <!-- Compatibility -->

-    <Match>

-        <Class name="org.apache.commons.dbutils.QueryLoader" />

-        <Bug pattern="SING_SINGLETON_HAS_NONPRIVATE_CONSTRUCTOR" />

-    </Match>

+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   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.
+-->
+<FindBugsFilter
+    xmlns="https://github.com/spotbugs/filter/3.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd">
+
+    <!-- TODO Can any of these be done without breaking binary compatibility? -->
+    <Match>
+        <Or>
+            <Bug pattern="EI_EXPOSE_REP" />
+            <Bug pattern="EI_EXPOSE_REP2" />
+        </Or>
+    </Match>
+    <!-- PrintWriter wraps System.err which always uses the default encoding -->
+    <Match>
+        <Class name="org.apache.commons.dbutils.DbUtils" />
+        <Method name="printStackTrace" />
+        <Bug pattern="DM_DEFAULT_ENCODING" />
+    </Match>
+    <!-- PrintWriter wraps System.err which always uses the default encoding -->
+    <Match>
+        <Class name="org.apache.commons.dbutils.DbUtils" />
+        <Method name="printWarnings" />
+        <Bug pattern="DM_DEFAULT_ENCODING" />
+    </Match>
+    <!-- The Javadocs state and the called code shows that null can be returned. -->
+    <Match>
+        <Class name="org.apache.commons.dbutils.QueryLoader" />
+        <Method name="loadQueries" />
+        <Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE" />
+    </Match>
+    <!-- This method returns a boolean indicating status. -->
+    <Match>
+        <Class name="org.apache.commons.dbutils.DbUtils" />
+        <Method name="loadDriver" />
+        <Bug pattern="REC_CATCH_EXCEPTION" />
+    </Match>
+    <!-- Compatibility -->
+    <Match>
+        <Class name="org.apache.commons.dbutils.BasicRowProcessor" />
+        <Bug pattern="SING_SINGLETON_HAS_NONPRIVATE_CONSTRUCTOR" />
+    </Match>
+    <!-- Compatibility -->
+    <Match>
+        <Class name="org.apache.commons.dbutils.ProxyFactory" />
+        <Bug pattern="SING_SINGLETON_HAS_NONPRIVATE_CONSTRUCTOR" />
+    </Match>
+    <!-- Compatibility -->
+    <Match>
+        <Class name="org.apache.commons.dbutils.QueryLoader" />
+        <Bug pattern="SING_SINGLETON_HAS_NONPRIVATE_CONSTRUCTOR" />
+    </Match>
 </FindBugsFilter>
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/dbutils/BaseTestCase.java b/src/test/java/org/apache/commons/dbutils/BaseTestCase.java
index d1f903b..66d8a59 100644
--- a/src/test/java/org/apache/commons/dbutils/BaseTestCase.java
+++ b/src/test/java/org/apache/commons/dbutils/BaseTestCase.java
@@ -1,122 +1,122 @@
-/*

- * 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.dbutils;

-

-import static org.junit.jupiter.api.Assertions.assertEquals;

-import static org.junit.jupiter.api.Assertions.assertFalse;

-

-import java.math.BigInteger;

-import java.sql.ResultSet;

-import java.sql.ResultSetMetaData;

-import java.sql.Timestamp;

-import java.util.Date;

-

-import org.junit.jupiter.api.BeforeEach;

-import org.junit.jupiter.api.Test;

-

-/**

- * BaseTestCase is the base class for all test cases as well as the "all tests" runner.

- */

-public class BaseTestCase {

-

-    private static final String[] columnNames = { "one", "two", "three", "notInBean", "intTest", "integerTest", "nullObjectTest", "nullPrimitiveTest",

-            "notDate", "columnProcessorDoubleTest", null };

-

-    /**

-     * The number of columns in the MockResultSet.

-     */

-    protected static final int COLS = columnNames.length;

-

-    protected static final ResultSetMetaData metaData = MockResultSetMetaData.create(columnNames);

-

-    /**

-     * A Timestamp for test purposes having 9 decimals

-     */

-    static final Timestamp ts789456123;

-

-    static {

-        ts789456123 = new Timestamp(new Date().getTime());

-        ts789456123.setNanos(789456123);

-    }

-

-    private static final Object[] row1 = { "1", "2", "THREE", "  notInBean  ", Integer.valueOf(1), Integer.valueOf(2), null, null, new Date(),

-            BigInteger.valueOf(13), null };

-

-    private static final Object[] row2 = { "4", "5", "SIX", "  notInBean  ", Integer.valueOf(3), Integer.valueOf(4), null, null, ts789456123,

-            BigInteger.valueOf(13), null };

-

-    private static final Object[][] rows = { row1, row2 };

-

-    /**

-     * The number of rows in the MockResultSet.

-     */

-    protected static final int ROWS = rows.length;

-

-    /**

-     * The ResultSet all test methods will use.

-     */

-    private ResultSet rs;

-

-    /**

-     * A ResultSet with 0 rows.

-     */

-    private ResultSet emptyResultSet;

-

-    /**

-     * Creates a freshly initialized ResultSet.

-     */

-    protected ResultSet createMockResultSet() {

-        return MockResultSet.create(metaData, rows);

-    }

-

-    public ResultSet getEmptyResultSet() {

-        return this.emptyResultSet;

-    }

-

-    public ResultSet getResultSet() {

-        return this.rs;

-    }

-

-    public void setResultSet(final ResultSet resultSet) {

-        this.rs = resultSet;

-    }

-

-    /**

-     * This is called before each test method so ResultSet will be fresh each time.

-     *

-     * @see org.junit.jupiter.api.BeforeEach

-     */

-    @BeforeEach

-    protected void setUp() throws Exception {

-        rs = createMockResultSet();

-        emptyResultSet = MockResultSet.create(metaData, null);

-    }

-

-    // Test which allows Eclipse to be run on full project (avoids no tests found)

-    // check that the rows are valid for the column definition

-    @Test

-    public void testCheckDataSizes() {

-        assertEquals(columnNames.length, row1.length, "Row 1 must contain correct number of columns");

-        assertEquals(columnNames.length, row2.length, "Row 1 must contain correct number of columns");

-    }

-

-    @Test

-    public void testResultSets() throws Exception {

-        assertFalse(emptyResultSet.next(), "emptyResultSet should be empty");

-        // fails in SqlNullCheckedResultSetTest assertTrue("rs should not be empty", rs.next());

-    }

-}

+/*
+ * 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.dbutils;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import java.math.BigInteger;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.Timestamp;
+import java.util.Date;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * BaseTestCase is the base class for all test cases as well as the "all tests" runner.
+ */
+public class BaseTestCase {
+
+    private static final String[] columnNames = { "one", "two", "three", "notInBean", "intTest", "integerTest", "nullObjectTest", "nullPrimitiveTest",
+            "notDate", "columnProcessorDoubleTest", null };
+
+    /**
+     * The number of columns in the MockResultSet.
+     */
+    protected static final int COLS = columnNames.length;
+
+    protected static final ResultSetMetaData metaData = MockResultSetMetaData.create(columnNames);
+
+    /**
+     * A Timestamp for test purposes having 9 decimals
+     */
+    static final Timestamp ts789456123;
+
+    static {
+        ts789456123 = new Timestamp(new Date().getTime());
+        ts789456123.setNanos(789456123);
+    }
+
+    private static final Object[] row1 = { "1", "2", "THREE", "  notInBean  ", Integer.valueOf(1), Integer.valueOf(2), null, null, new Date(),
+            BigInteger.valueOf(13), null };
+
+    private static final Object[] row2 = { "4", "5", "SIX", "  notInBean  ", Integer.valueOf(3), Integer.valueOf(4), null, null, ts789456123,
+            BigInteger.valueOf(13), null };
+
+    private static final Object[][] rows = { row1, row2 };
+
+    /**
+     * The number of rows in the MockResultSet.
+     */
+    protected static final int ROWS = rows.length;
+
+    /**
+     * The ResultSet all test methods will use.
+     */
+    private ResultSet rs;
+
+    /**
+     * A ResultSet with 0 rows.
+     */
+    private ResultSet emptyResultSet;
+
+    /**
+     * Creates a freshly initialized ResultSet.
+     */
+    protected ResultSet createMockResultSet() {
+        return MockResultSet.create(metaData, rows);
+    }
+
+    public ResultSet getEmptyResultSet() {
+        return this.emptyResultSet;
+    }
+
+    public ResultSet getResultSet() {
+        return this.rs;
+    }
+
+    public void setResultSet(final ResultSet resultSet) {
+        this.rs = resultSet;
+    }
+
+    /**
+     * This is called before each test method so ResultSet will be fresh each time.
+     *
+     * @see org.junit.jupiter.api.BeforeEach
+     */
+    @BeforeEach
+    protected void setUp() throws Exception {
+        rs = createMockResultSet();
+        emptyResultSet = MockResultSet.create(metaData, null);
+    }
+
+    // Test which allows Eclipse to be run on full project (avoids no tests found)
+    // check that the rows are valid for the column definition
+    @Test
+    public void testCheckDataSizes() {
+        assertEquals(columnNames.length, row1.length, "Row 1 must contain correct number of columns");
+        assertEquals(columnNames.length, row2.length, "Row 1 must contain correct number of columns");
+    }
+
+    @Test
+    public void testResultSets() throws Exception {
+        assertFalse(emptyResultSet.next(), "emptyResultSet should be empty");
+        // fails in SqlNullCheckedResultSetTest assertTrue("rs should not be empty", rs.next());
+    }
+}
diff --git a/src/test/java/org/apache/commons/dbutils/wrappers/SqlNullCheckedResultSetTest.java b/src/test/java/org/apache/commons/dbutils/wrappers/SqlNullCheckedResultSetTest.java
index a39c9c2..36cbeed 100644
--- a/src/test/java/org/apache/commons/dbutils/wrappers/SqlNullCheckedResultSetTest.java
+++ b/src/test/java/org/apache/commons/dbutils/wrappers/SqlNullCheckedResultSetTest.java
@@ -1,1085 +1,1085 @@
-/*

- * 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.dbutils.wrappers;

-

-import static org.junit.jupiter.api.Assertions.assertEquals;

-import static org.junit.jupiter.api.Assertions.assertFalse;

-import static org.junit.jupiter.api.Assertions.assertNotNull;

-import static org.junit.jupiter.api.Assertions.assertNull;

-import static org.junit.jupiter.api.Assertions.assertTrue;

-import static org.mockito.Mockito.mock;

-import static org.mockito.Mockito.verify;

-

-import java.io.ByteArrayInputStream;

-import java.io.CharArrayReader;

-import java.io.InputStream;

-import java.io.OutputStream;

-import java.io.Reader;

-import java.io.Writer;

-import java.lang.reflect.InvocationHandler;

-import java.lang.reflect.InvocationTargetException;

-import java.lang.reflect.Method;

-import java.math.BigDecimal;

-import java.net.MalformedURLException;

-import java.net.URL;

-import java.sql.Blob;

-import java.sql.Clob;

-import java.sql.Ref;

-import java.sql.ResultSet;

-import java.sql.SQLException;

-import java.sql.Time;

-import java.sql.Timestamp;

-import java.util.Arrays;

-import java.util.Calendar;

-import java.util.Map;

-

-import org.apache.commons.dbutils.BaseTestCase;

-import org.apache.commons.dbutils.ProxyFactory;

-import org.junit.jupiter.api.BeforeEach;

-import org.junit.jupiter.api.Test;

-

-final class SqlNullCheckedResultSetMockBlob implements Blob {

-

-    /**

-     * @throws SQLException

-     */

-    @Override

-    public void free() throws SQLException {

-

-    }

-

-    @Override

-    public InputStream getBinaryStream() throws SQLException {

-        return new ByteArrayInputStream(new byte[0]);

-    }

-

-    /**

-     * @throws SQLException

-     */

-    @Override

-    public InputStream getBinaryStream(final long pos, final long length) throws SQLException {

-        return null;

-    }

-

-    @Override

-    public byte[] getBytes(final long param, final int param1) throws SQLException {

-        return new byte[0];

-    }

-

-    @Override

-    public long length() throws SQLException {

-        return 0;

-    }

-

-    @Override

-    public long position(final Blob blob, final long param) throws SQLException {

-        return 0;

-    }

-

-    @Override

-    public long position(final byte[] values, final long param) throws SQLException {

-        return 0;

-    }

-

-    @Override

-    public OutputStream setBinaryStream(final long pos) throws SQLException {

-        return null;

-    }

-

-    @Override

-    public int setBytes(final long pos, final byte[] bytes) throws SQLException {

-        return 0;

-    }

-

-    @Override

-    public int setBytes(final long pos, final byte[] bytes, final int offset, final int len) throws SQLException {

-        return 0;

-    }

-

-    @Override

-    public void truncate(final long len) throws SQLException {

-

-    }

-

-}

-

-final class SqlNullCheckedResultSetMockClob implements Clob {

-

-    /**

-     * @throws SQLException

-     */

-    @Override

-    public void free() throws SQLException {

-

-    }

-

-    @Override

-    public InputStream getAsciiStream() throws SQLException {

-        return null;

-    }

-

-    @Override

-    public Reader getCharacterStream() throws SQLException {

-        return null;

-    }

-

-    /**

-     * @throws SQLException

-     */

-    @Override

-    public Reader getCharacterStream(final long pos, final long length) throws SQLException {

-        return null;

-    }

-

-    @Override

-    public String getSubString(final long param, final int param1) throws SQLException {

-        return "";

-    }

-

-    @Override

-    public long length() throws SQLException {

-        return 0;

-    }

-

-    @Override

-    public long position(final Clob clob, final long param) throws SQLException {

-        return 0;

-    }

-

-    @Override

-    public long position(final String str, final long param) throws SQLException {

-        return 0;

-    }

-

-    @Override

-    public OutputStream setAsciiStream(final long pos) throws SQLException {

-        return null;

-    }

-

-    @Override

-    public Writer setCharacterStream(final long pos) throws SQLException {

-        return null;

-    }

-

-    @Override

-    public int setString(final long pos, final String str) throws SQLException {

-        return 0;

-    }

-

-    @Override

-    public int setString(final long pos, final String str, final int offset, final int len) throws SQLException {

-        return 0;

-    }

-

-    @Override

-    public void truncate(final long len) throws SQLException {

-

-    }

-

-}

-

-final class SqlNullCheckedResultSetMockRef implements Ref {

-

-    @Override

-    public String getBaseTypeName() throws SQLException {

-        return "";

-    }

-

-    @Override

-    public Object getObject() throws SQLException {

-        return null;

-    }

-

-    @Override

-    public Object getObject(final Map<String, Class<?>> map) throws SQLException {

-        return null;

-    }

-

-    @Override

-    public void setObject(final Object value) throws SQLException {

-        // no-op

-    }

-

-}

-

-/**

- * Test cases for {@code SqlNullCheckedResultSet} class.

- */

-public class SqlNullCheckedResultSetTest extends BaseTestCase {

-

-    private static ResultSet rs;

-

-    private static void assertArrayEquals(final byte[] expected, final byte[] actual) {

-        if (expected == actual) {

-            return;

-        }

-        if (expected.length != actual.length) {

-            assertEquals(Arrays.toString(expected), Arrays.toString(actual));

-        }

-        for (int i = 0; i < expected.length; i++) {

-            final byte expectedItem = expected[i];

-            final byte actualItem = actual[i];

-            assertEquals(expectedItem, actualItem, "Array not equal at index " + i);

-        }

-    }

-    private SqlNullCheckedResultSet rs2;

-

-    /**

-     * Sets up instance variables required by this test case.

-     */

-    @BeforeEach

-    public void setUp() throws Exception {

-        super.setUp();

-

-        rs2 = new SqlNullCheckedResultSet(ProxyFactory.instance().createResultSet(new SqlNullUncheckedMockResultSet()));

-

-        setResultSet(ProxyFactory.instance().createResultSet(rs2)); // Override superclass field

-        rs = getResultSet();

-    }

-

-    /**

-     * Tests the getAsciiStream implementation.

-     */

-    @Test

-    public void testGetAsciiStream() throws SQLException {

-

-        assertNull(rs.getAsciiStream(1));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getAsciiStream("column"));

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final InputStream stream = new ByteArrayInputStream(new byte[0]);

-        rs2.setNullAsciiStream(stream);

-        assertNotNull(rs.getAsciiStream(1));

-        assertEquals(stream, rs.getAsciiStream(1));

-        assertNotNull(rs.getAsciiStream("column"));

-        assertEquals(stream, rs.getAsciiStream("column"));

-

-    }

-

-    /**

-     * Tests the getBigDecimal implementation.

-     */

-    @Test

-    public void testGetBigDecimal() throws SQLException {

-

-        assertNull(rs.getBigDecimal(1));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getBigDecimal("column"));

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final BigDecimal bd = new BigDecimal(5.0);

-        rs2.setNullBigDecimal(bd);

-        assertNotNull(rs.getBigDecimal(1));

-        assertEquals(bd, rs.getBigDecimal(1));

-        assertNotNull(rs.getBigDecimal("column"));

-        assertEquals(bd, rs.getBigDecimal("column"));

-

-    }

-

-    /**

-     * Tests the getBinaryStream implementation.

-     */

-    @Test

-    public void testGetBinaryStream() throws SQLException {

-

-        assertNull(rs.getBinaryStream(1));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getBinaryStream("column"));

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final InputStream stream = new ByteArrayInputStream(new byte[0]);

-        rs2.setNullBinaryStream(stream);

-        assertNotNull(rs.getBinaryStream(1));

-        assertEquals(stream, rs.getBinaryStream(1));

-        assertNotNull(rs.getBinaryStream("column"));

-        assertEquals(stream, rs.getBinaryStream("column"));

-

-    }

-

-    /**

-     * Tests the getBlob implementation.

-     */

-    @Test

-    public void testGetBlob() throws SQLException {

-

-        assertNull(rs.getBlob(1));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getBlob("column"));

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final Blob blob = new SqlNullCheckedResultSetMockBlob();

-        rs2.setNullBlob(blob);

-        assertNotNull(rs.getBlob(1));

-        assertEquals(blob, rs.getBlob(1));

-        assertNotNull(rs.getBlob("column"));

-        assertEquals(blob, rs.getBlob("column"));

-

-    }

-

-    /**

-     * Tests the getBoolean implementation.

-     */

-    @Test

-    public void testGetBoolean() throws SQLException {

-

-        assertFalse(rs.getBoolean(1));

-        assertTrue(rs.wasNull());

-        assertFalse(rs.getBoolean("column"));

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        rs2.setNullBoolean(true);

-        assertTrue( rs.getBoolean(1));

-        assertTrue( rs.getBoolean("column"));

-

-    }

-

-    /**

-     * Tests the getByte implementation.

-     */

-    @Test

-    public void testGetByte() throws SQLException {

-

-        assertEquals((byte) 0, rs.getByte(1));

-        assertTrue(rs.wasNull());

-        assertEquals((byte) 0, rs.getByte("column"));

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final byte b = (byte) 10;

-        rs2.setNullByte(b);

-        assertEquals(b, rs.getByte(1));

-        assertEquals(b, rs.getByte("column"));

-

-    }

-

-    /**

-     * Tests the getByte implementation.

-     */

-    @Test

-    public void testGetBytes() throws SQLException {

-

-        assertNull(rs.getBytes(1));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getBytes("column"));

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final byte[] b = new byte[5];

-        for (int i = 0; i < 5; i++) {

-            b[0] = (byte) i;

-        }

-        rs2.setNullBytes(b);

-        assertNotNull(rs.getBytes(1));

-        assertArrayEquals(b, rs.getBytes(1));

-        assertNotNull(rs.getBytes("column"));

-        assertArrayEquals(b, rs.getBytes("column"));

-

-    }

-

-    /**

-     * Tests the getCharacterStream implementation.

-     */

-    @Test

-    public void testGetCharacterStream() throws SQLException {

-

-        assertNull(rs.getCharacterStream(1));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getCharacterStream("column"));

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final Reader reader = new CharArrayReader("this is a string".toCharArray());

-        rs2.setNullCharacterStream(reader);

-        assertNotNull(rs.getCharacterStream(1));

-        assertEquals(reader, rs.getCharacterStream(1));

-        assertNotNull(rs.getCharacterStream("column"));

-        assertEquals(reader, rs.getCharacterStream("column"));

-

-    }

-

-    /**

-     * Tests the getClob implementation.

-     */

-    @Test

-    public void testGetClob() throws SQLException {

-

-        assertNull(rs.getClob(1));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getClob("column"));

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final Clob clob = new SqlNullCheckedResultSetMockClob();

-        rs2.setNullClob(clob);

-        assertNotNull(rs.getClob(1));

-        assertEquals(clob, rs.getClob(1));

-        assertNotNull(rs.getClob("column"));

-        assertEquals(clob, rs.getClob("column"));

-

-    }

-

-    /**

-     * Tests the getDate implementation.

-     */

-    @Test

-    public void testGetDate() throws SQLException {

-

-        assertNull(rs.getDate(1));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getDate("column"));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getDate(1, Calendar.getInstance()));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getDate("column", Calendar.getInstance()));

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final java.sql.Date date = new java.sql.Date(new java.util.Date().getTime());

-        rs2.setNullDate(date);

-        assertNotNull(rs.getDate(1));

-        assertEquals(date, rs.getDate(1));

-        assertNotNull(rs.getDate("column"));

-        assertEquals(date, rs.getDate("column"));

-        assertNotNull(rs.getDate(1, Calendar.getInstance()));

-        assertEquals(date, rs.getDate(1, Calendar.getInstance()));

-        assertNotNull(rs.getDate("column", Calendar.getInstance()));

-        assertEquals(date, rs.getDate("column", Calendar.getInstance()));

-

-    }

-

-    /**

-     * Tests the getDouble implementation.

-     */

-    @Test

-    public void testGetDouble() throws SQLException {

-

-        assertEquals(0.0, rs.getDouble(1), 0.0);

-        assertTrue(rs.wasNull());

-        assertEquals(0.0, rs.getDouble("column"), 0.0);

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final double d = 10.0;

-        rs2.setNullDouble(d);

-        assertEquals(d, rs.getDouble(1), 0.0);

-        assertEquals(d, rs.getDouble("column"), 0.0);

-

-    }

-

-    /**

-     * Tests the getFloat implementation.

-     */

-    @Test

-    public void testGetFloat() throws SQLException {

-        assertEquals(0, rs.getFloat(1), 0.0);

-        assertTrue(rs.wasNull());

-        assertEquals(0, rs.getFloat("column"), 0.0);

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final float f = 10;

-        rs2.setNullFloat(f);

-        assertEquals(f, rs.getFloat(1), 0.0);

-        assertEquals(f, rs.getFloat("column"), 0.0);

-    }

-

-    /**

-     * Tests the getInt implementation.

-     */

-    @Test

-    public void testGetInt() throws SQLException {

-        assertEquals(0, rs.getInt(1));

-        assertTrue(rs.wasNull());

-        assertEquals(0, rs.getInt("column"));

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final int i = 10;

-        rs2.setNullInt(i);

-        assertEquals(i, rs.getInt(1));

-        assertEquals(i, rs.getInt("column"));

-    }

-

-    /**

-     * Tests the getLong implementation.

-     */

-    @Test

-    public void testGetLong() throws SQLException {

-        assertEquals(0, rs.getLong(1));

-        assertTrue(rs.wasNull());

-        assertEquals(0, rs.getLong("column"));

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final long l = 10;

-        rs2.setNullLong(l);

-        assertEquals(l, rs.getLong(1));

-        assertEquals(l, rs.getLong("column"));

-    }

-

-    /**

-     * Tests the getObject implementation.

-     */

-    @Test

-    public void testGetObject() throws SQLException {

-

-        assertNull(rs.getObject(1));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getObject("column"));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getObject(1, (Map<String, Class<?>>) null));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getObject("column", (Map<String, Class<?>>) null));

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final Object o = new Object();

-        rs2.setNullObject(o);

-        assertNotNull(rs.getObject(1));

-        assertEquals(o, rs.getObject(1));

-        assertNotNull(rs.getObject("column"));

-        assertEquals(o, rs.getObject("column"));

-        assertNotNull(rs.getObject(1, (Map<String, Class<?>>) null));

-        assertEquals(o, rs.getObject(1, (Map<String, Class<?>>) null));

-        assertNotNull(rs.getObject("column", (Map<String, Class<?>>) null));

-        assertEquals(o, rs.getObject("column", (Map<String, Class<?>>) null));

-

-    }

-

-    /**

-     * Tests the getRef implementation.

-     */

-    @Test

-    public void testGetRef() throws SQLException {

-

-        assertNull(rs.getRef(1));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getRef("column"));

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final Ref ref = new SqlNullCheckedResultSetMockRef();

-        rs2.setNullRef(ref);

-        assertNotNull(rs.getRef(1));

-        assertEquals(ref, rs.getRef(1));

-        assertNotNull(rs.getRef("column"));

-        assertEquals(ref, rs.getRef("column"));

-

-    }

-

-    /**

-     * Tests the getShort implementation.

-     */

-    @Test

-    public void testGetShort() throws SQLException {

-

-        assertEquals((short) 0, rs.getShort(1));

-        assertTrue(rs.wasNull());

-        assertEquals((short) 0, rs.getShort("column"));

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final short s = (short) 10;

-        rs2.setNullShort(s);

-        assertEquals(s, rs.getShort(1));

-        assertEquals(s, rs.getShort("column"));

-    }

-

-    /**

-     * Tests the getString implementation.

-     */

-    @Test

-    public void testGetString() throws SQLException {

-        assertNull(rs.getString(1));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getString("column"));

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final String s = "hello, world";

-        rs2.setNullString(s);

-        assertEquals(s, rs.getString(1));

-        assertEquals(s, rs.getString("column"));

-    }

-

-    /**

-     * Tests the getTime implementation.

-     */

-    @Test

-    public void testGetTime() throws SQLException {

-

-        assertNull(rs.getTime(1));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getTime("column"));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getTime(1, Calendar.getInstance()));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getTime("column", Calendar.getInstance()));

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final Time time = new Time(new java.util.Date().getTime());

-        rs2.setNullTime(time);

-        assertNotNull(rs.getTime(1));

-        assertEquals(time, rs.getTime(1));

-        assertNotNull(rs.getTime("column"));

-        assertEquals(time, rs.getTime("column"));

-        assertNotNull(rs.getTime(1, Calendar.getInstance()));

-        assertEquals(time, rs.getTime(1, Calendar.getInstance()));

-        assertNotNull(rs.getTime("column", Calendar.getInstance()));

-        assertEquals(time, rs.getTime("column", Calendar.getInstance()));

-

-    }

-

-    /**

-     * Tests the getTimestamp implementation.

-     */

-    @Test

-    public void testGetTimestamp() throws SQLException {

-

-        assertNull(rs.getTimestamp(1));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getTimestamp("column"));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getTimestamp(1, Calendar.getInstance()));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getTimestamp("column", Calendar.getInstance()));

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final Timestamp ts = new Timestamp(new java.util.Date().getTime());

-        rs2.setNullTimestamp(ts);

-        assertNotNull(rs.getTimestamp(1));

-        assertEquals(ts, rs.getTimestamp(1));

-        assertNotNull(rs.getTimestamp("column"));

-        assertEquals(ts, rs.getTimestamp("column"));

-        assertNotNull(rs.getTimestamp(1, Calendar.getInstance()));

-        assertEquals(ts, rs.getTimestamp(1, Calendar.getInstance()));

-        assertNotNull(rs.getTimestamp("column", Calendar.getInstance()));

-        assertEquals(ts, rs.getTimestamp("column", Calendar.getInstance()));

-    }

-

-    /**

-     * Tests the setNullAsciiStream implementation.

-     */

-    @Test

-    public void testSetNullAsciiStream() throws SQLException {

-

-        assertNull(rs2.getNullAsciiStream());

-        // Set what gets returned to something other than the default

-        final InputStream stream = new ByteArrayInputStream(new byte[0]);

-        rs2.setNullAsciiStream(stream);

-        assertNotNull(rs.getAsciiStream(1));

-        assertEquals(stream, rs.getAsciiStream(1));

-        assertNotNull(rs.getAsciiStream("column"));

-        assertEquals(stream, rs.getAsciiStream("column"));

-

-    }

-

-    /**

-     * Tests the setNullBigDecimal implementation.

-     */

-    @Test

-    public void testSetNullBigDecimal() throws SQLException {

-

-        assertNull(rs2.getNullBigDecimal());

-        // Set what gets returned to something other than the default

-        final BigDecimal bd = new BigDecimal(5.0);

-        rs2.setNullBigDecimal(bd);

-        assertNotNull(rs.getBigDecimal(1));

-        assertEquals(bd, rs.getBigDecimal(1));

-        assertNotNull(rs.getBigDecimal("column"));

-        assertEquals(bd, rs.getBigDecimal("column"));

-

-    }

-

-    /**

-     * Tests the setNullBinaryStream implementation.

-     */

-    @Test

-    public void testSetNullBinaryStream() throws SQLException {

-

-        assertNull(rs2.getNullBinaryStream());

-        // Set what gets returned to something other than the default

-        final InputStream stream = new ByteArrayInputStream(new byte[0]);

-        rs2.setNullBinaryStream(stream);

-        assertNotNull(rs.getBinaryStream(1));

-        assertEquals(stream, rs.getBinaryStream(1));

-        assertNotNull(rs.getBinaryStream("column"));

-        assertEquals(stream, rs.getBinaryStream("column"));

-

-    }

-

-    /**

-     * Tests the setNullBlob implementation.

-     */

-    @Test

-    public void testSetNullBlob() throws SQLException {

-

-        assertNull(rs2.getNullBlob());

-        // Set what gets returned to something other than the default

-        final Blob blob = new SqlNullCheckedResultSetMockBlob();

-        rs2.setNullBlob(blob);

-        assertNotNull(rs.getBlob(1));

-        assertEquals(blob, rs.getBlob(1));

-        assertNotNull(rs.getBlob("column"));

-        assertEquals(blob, rs.getBlob("column"));

-

-    }

-

-    /**

-     * Tests the setNullBoolean implementation.

-     */

-    @Test

-    public void testSetNullBoolean() throws SQLException {

-

-        assertFalse(rs2.getNullBoolean());

-        // Set what gets returned to something other than the default

-        rs2.setNullBoolean(true);

-        assertTrue( rs.getBoolean(1));

-        assertTrue( rs.getBoolean("column"));

-

-    }

-

-    /**

-     * Tests the setNullByte implementation.

-     */

-    @Test

-    public void testSetNullByte() throws SQLException {

-

-        assertEquals((byte) 0, rs2.getNullByte());

-        // Set what gets returned to something other than the default

-        final byte b = (byte) 10;

-        rs2.setNullByte(b);

-        assertEquals(b, rs.getByte(1));

-        assertEquals(b, rs.getByte("column"));

-

-    }

-

-    /**

-     * Tests the setNullByte implementation.

-     */

-    @Test

-    public void testSetNullBytes() throws SQLException {

-        // test the default, unset value

-        assertNull(rs2.getNullBytes());

-

-        // test that setting null is safe

-        rs2.setNullBytes(null);

-        assertNull(rs2.getNullBytes());

-

-        // Set what gets returned to something other than the default

-        final byte[] b = new byte[5];

-        for (int i = 0; i < 5; i++) {

-            b[0] = (byte) i;

-        }

-        rs2.setNullBytes(b);

-        assertNotNull(rs.getBytes(1));

-        assertArrayEquals(b, rs.getBytes(1));

-        assertNotNull(rs.getBytes("column"));

-        assertArrayEquals(b, rs.getBytes("column"));

-

-    }

-

-    /**

-     * Tests the setNullCharacterStream implementation.

-     */

-    @Test

-    public void testSetNullCharacterStream() throws SQLException {

-

-        assertNull(rs2.getNullCharacterStream());

-        // Set what gets returned to something other than the default

-        final Reader reader = new CharArrayReader("this is a string".toCharArray());

-        rs2.setNullCharacterStream(reader);

-        assertNotNull(rs.getCharacterStream(1));

-        assertEquals(reader, rs.getCharacterStream(1));

-        assertNotNull(rs.getCharacterStream("column"));

-        assertEquals(reader, rs.getCharacterStream("column"));

-

-    }

-

-    /**

-     * Tests the setNullClob implementation.

-     */

-    @Test

-    public void testSetNullClob() throws SQLException {

-

-        assertNull(rs2.getNullClob());

-        // Set what gets returned to something other than the default

-        final Clob clob = new SqlNullCheckedResultSetMockClob();

-        rs2.setNullClob(clob);

-        assertNotNull(rs.getClob(1));

-        assertEquals(clob, rs.getClob(1));

-        assertNotNull(rs.getClob("column"));

-        assertEquals(clob, rs.getClob("column"));

-

-    }

-

-    /**

-     * Tests the setNullDate implementation.

-     */

-    @Test

-    public void testSetNullDate() throws SQLException {

-        // test the default, unset value

-        assertNull(rs2.getNullDate());

-

-        // test that setting null is safe

-        rs2.setNullDate(null);

-        assertNull(rs2.getNullDate());

-

-        // Set what gets returned to something other than the default

-        final java.sql.Date date = new java.sql.Date(new java.util.Date().getTime());

-        rs2.setNullDate(date);

-        assertNotNull(rs.getDate(1));

-        assertEquals(date, rs.getDate(1));

-        assertNotNull(rs.getDate("column"));

-        assertEquals(date, rs.getDate("column"));

-        assertNotNull(rs.getDate(1, Calendar.getInstance()));

-        assertEquals(date, rs.getDate(1, Calendar.getInstance()));

-        assertNotNull(rs.getDate("column", Calendar.getInstance()));

-        assertEquals(date, rs.getDate("column", Calendar.getInstance()));

-

-    }

-

-    /**

-     * Tests the setNullDouble implementation.

-     */

-    @Test

-    public void testSetNullDouble() throws SQLException {

-        assertEquals(0.0, rs2.getNullDouble(), 0.0);

-        // Set what gets returned to something other than the default

-        final double d = 10.0;

-        rs2.setNullDouble(d);

-        assertEquals(d, rs.getDouble(1), 0.0);

-        assertEquals(d, rs.getDouble("column"), 0.0);

-    }

-

-    /**

-     * Tests the setNullFloat implementation.

-     */

-    @Test

-    public void testSetNullFloat() throws SQLException {

-        assertEquals((float) 0.0, rs2.getNullFloat(), 0.0);

-        // Set what gets returned to something other than the default

-        final float f = (float) 10.0;

-        rs2.setNullFloat(f);

-        assertEquals(f, rs.getFloat(1), 0.0);

-        assertEquals(f, rs.getFloat("column"), 0.0);

-    }

-

-    /**

-     * Tests the setNullInt implementation.

-     */

-    @Test

-    public void testSetNullInt() throws SQLException {

-        assertEquals(0, rs2.getNullInt());

-        assertEquals(0, rs.getInt(1));

-        assertTrue(rs.wasNull());

-        assertEquals(0, rs.getInt("column"));

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final int i = 10;

-        rs2.setNullInt(i);

-        assertEquals(i, rs.getInt(1));

-        assertEquals(i, rs.getInt("column"));

-    }

-

-    /**

-     * Tests the setNullLong implementation.

-     */

-    @Test

-    public void testSetNullLong() throws SQLException {

-        assertEquals(0, rs2.getNullLong());

-        // Set what gets returned to something other than the default

-        final long l = 10;

-        rs2.setNullLong(l);

-        assertEquals(l, rs.getLong(1));

-        assertEquals(l, rs.getLong("column"));

-    }

-

-    /**

-     * Tests the setNullObject implementation.

-     */

-    @Test

-    public void testSetNullObject() throws SQLException {

-        assertNull(rs2.getNullObject());

-        // Set what gets returned to something other than the default

-        final Object o = new Object();

-        rs2.setNullObject(o);

-        assertNotNull(rs.getObject(1));

-        assertEquals(o, rs.getObject(1));

-        assertNotNull(rs.getObject("column"));

-        assertEquals(o, rs.getObject("column"));

-        assertNotNull(rs.getObject(1, (Map<String, Class<?>>) null));

-        assertEquals(o, rs.getObject(1, (Map<String, Class<?>>) null));

-        assertNotNull(rs.getObject("column", (Map<String, Class<?>>) null));

-        assertEquals(o, rs.getObject("column", (Map<String, Class<?>>) null));

-    }

-

-    /**

-     * Tests the setNullRef implementation.

-     */

-    @Test

-    public void testSetNullRef() throws SQLException {

-        assertNull(rs2.getNullRef());

-        // Set what gets returned to something other than the default

-        final Ref ref = new SqlNullCheckedResultSetMockRef();

-        rs2.setNullRef(ref);

-        assertNotNull(rs.getRef(1));

-        assertEquals(ref, rs.getRef(1));

-        assertNotNull(rs.getRef("column"));

-        assertEquals(ref, rs.getRef("column"));

-    }

-

-    /**

-     * Tests the setNullShort implementation.

-     */

-    @Test

-    public void testSetNullShort() throws SQLException {

-

-        assertEquals((short) 0, rs2.getNullShort());

-        // Set what gets returned to something other than the default

-        final short s = (short) 10;

-        rs2.setNullShort(s);

-        assertEquals(s, rs.getShort(1));

-        assertEquals(s, rs.getShort("column"));

-

-    }

-

-    /**

-     * Tests the setNullString implementation.

-     */

-    @Test

-    public void testSetNullString() throws SQLException {

-        assertNull(rs2.getNullString());

-        // Set what gets returned to something other than the default

-        final String s = "hello, world";

-        rs2.setNullString(s);

-        assertEquals(s, rs.getString(1));

-        assertEquals(s, rs.getString("column"));

-    }

-

-    /**

-     * Tests the setNullTime implementation.

-     */

-    @Test

-    public void testSetNullTime() throws SQLException {

-        // test the default, unset value

-        assertNull(rs2.getNullTime());

-

-        // test that setting null is safe

-        rs2.setNullTime(null);

-        assertNull(rs2.getNullTime());

-

-        // Set what gets returned to something other than the default

-        final Time time = new Time(new java.util.Date().getTime());

-        rs2.setNullTime(time);

-        assertNotNull(rs.getTime(1));

-        assertEquals(time, rs.getTime(1));

-        assertNotNull(rs.getTime("column"));

-        assertEquals(time, rs.getTime("column"));

-        assertNotNull(rs.getTime(1, Calendar.getInstance()));

-        assertEquals(time, rs.getTime(1, Calendar.getInstance()));

-        assertNotNull(rs.getTime("column", Calendar.getInstance()));

-        assertEquals(time, rs.getTime("column", Calendar.getInstance()));

-    }

-

-    /**

-     * Tests the setNullTimestamp implementation.

-     */

-    @Test

-    public void testSetNullTimestamp() throws SQLException {

-        // test the default, unset value

-        assertNull(rs2.getNullTimestamp());

-

-        // test that setting null is safe

-        rs2.setNullTimestamp(null);

-        assertNull(rs2.getNullTimestamp());

-

-        // Set what gets returned to something other than the default

-        final Timestamp ts = new Timestamp(new java.util.Date().getTime());

-        rs2.setNullTimestamp(ts);

-        assertNotNull(rs.getTimestamp(1));

-        assertEquals(ts, rs.getTimestamp(1));

-        assertNotNull(rs.getTimestamp("column"));

-        assertEquals(ts, rs.getTimestamp("column"));

-        assertNotNull(rs.getTimestamp(1, Calendar.getInstance()));

-        assertEquals(ts, rs.getTimestamp(1, Calendar.getInstance()));

-        assertNotNull(rs.getTimestamp("column", Calendar.getInstance()));

-        assertEquals(ts, rs.getTimestamp("column", Calendar.getInstance()));

-    }

-

-    /**

-     * Tests the getURL and setNullURL implementations.

-     */

-    @Test

-    public void testURL() throws SQLException, MalformedURLException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {

-        assertNull(rs.getURL(1));

-        assertTrue(rs.wasNull());

-        assertNull(rs.getURL("column"));

-        assertTrue(rs.wasNull());

-        // Set what gets returned to something other than the default

-        final URL u = new URL("http://www.apache.org");

-        rs2.setNullURL(u);

-        assertEquals(u, rs.getURL(1));

-        assertEquals(u, rs.getURL("column"));

-    }

-

-    @Test

-    public void testWrapResultSet() throws SQLException {

-        final ResultSet wrappedRs = mock(ResultSet.class);

-        final ResultSet rs = SqlNullCheckedResultSet.wrap(wrappedRs);

-        rs.beforeFirst();

-        verify(wrappedRs).beforeFirst();

-        rs.next();

-        verify(wrappedRs).next();

-    }

-}

-

-final class SqlNullUncheckedMockResultSet implements InvocationHandler {

-

-    /**

-     * Always return false for booleans, 0 for numerics, and null for Objects.

-     *

-     * @see java.lang.reflect.InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])

-     */

-    @Override

-    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {

-

-        final Class<?> returnType = method.getReturnType();

-

-        if (method.getName().equals("wasNull")) {

-            return Boolean.TRUE;

-

-        }

-        if (returnType.equals(Boolean.TYPE)) {

-            return Boolean.FALSE;

-

-        }

-        if (returnType.equals(Integer.TYPE)) {

-            return Integer.valueOf(0);

-

-        }

-        if (returnType.equals(Short.TYPE)) {

-            return Short.valueOf((short) 0);

-

-        }

-        if (returnType.equals(Double.TYPE)) {

-            return Double.valueOf(0);

-

-        }

-        if (returnType.equals(Long.TYPE)) {

-            return Long.valueOf(0);

-

-        }

-        if (returnType.equals(Byte.TYPE)) {

-            return Byte.valueOf((byte) 0);

-

-        }

-        if (returnType.equals(Float.TYPE)) {

-            return Float.valueOf(0);

-

-        }

-        return null;

-    }

-}

+/*
+ * 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.dbutils.wrappers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.io.ByteArrayInputStream;
+import java.io.CharArrayReader;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Map;
+
+import org.apache.commons.dbutils.BaseTestCase;
+import org.apache.commons.dbutils.ProxyFactory;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+final class SqlNullCheckedResultSetMockBlob implements Blob {
+
+    /**
+     * @throws SQLException
+     */
+    @Override
+    public void free() throws SQLException {
+
+    }
+
+    @Override
+    public InputStream getBinaryStream() throws SQLException {
+        return new ByteArrayInputStream(new byte[0]);
+    }
+
+    /**
+     * @throws SQLException
+     */
+    @Override
+    public InputStream getBinaryStream(final long pos, final long length) throws SQLException {
+        return null;
+    }
+
+    @Override
+    public byte[] getBytes(final long param, final int param1) throws SQLException {
+        return new byte[0];
+    }
+
+    @Override
+    public long length() throws SQLException {
+        return 0;
+    }
+
+    @Override
+    public long position(final Blob blob, final long param) throws SQLException {
+        return 0;
+    }
+
+    @Override
+    public long position(final byte[] values, final long param) throws SQLException {
+        return 0;
+    }
+
+    @Override
+    public OutputStream setBinaryStream(final long pos) throws SQLException {
+        return null;
+    }
+
+    @Override
+    public int setBytes(final long pos, final byte[] bytes) throws SQLException {
+        return 0;
+    }
+
+    @Override
+    public int setBytes(final long pos, final byte[] bytes, final int offset, final int len) throws SQLException {
+        return 0;
+    }
+
+    @Override
+    public void truncate(final long len) throws SQLException {
+
+    }
+
+}
+
+final class SqlNullCheckedResultSetMockClob implements Clob {
+
+    /**
+     * @throws SQLException
+     */
+    @Override
+    public void free() throws SQLException {
+
+    }
+
+    @Override
+    public InputStream getAsciiStream() throws SQLException {
+        return null;
+    }
+
+    @Override
+    public Reader getCharacterStream() throws SQLException {
+        return null;
+    }
+
+    /**
+     * @throws SQLException
+     */
+    @Override
+    public Reader getCharacterStream(final long pos, final long length) throws SQLException {
+        return null;
+    }
+
+    @Override
+    public String getSubString(final long param, final int param1) throws SQLException {
+        return "";
+    }
+
+    @Override
+    public long length() throws SQLException {
+        return 0;
+    }
+
+    @Override
+    public long position(final Clob clob, final long param) throws SQLException {
+        return 0;
+    }
+
+    @Override
+    public long position(final String str, final long param) throws SQLException {
+        return 0;
+    }
+
+    @Override
+    public OutputStream setAsciiStream(final long pos) throws SQLException {
+        return null;
+    }
+
+    @Override
+    public Writer setCharacterStream(final long pos) throws SQLException {
+        return null;
+    }
+
+    @Override
+    public int setString(final long pos, final String str) throws SQLException {
+        return 0;
+    }
+
+    @Override
+    public int setString(final long pos, final String str, final int offset, final int len) throws SQLException {
+        return 0;
+    }
+
+    @Override
+    public void truncate(final long len) throws SQLException {
+
+    }
+
+}
+
+final class SqlNullCheckedResultSetMockRef implements Ref {
+
+    @Override
+    public String getBaseTypeName() throws SQLException {
+        return "";
+    }
+
+    @Override
+    public Object getObject() throws SQLException {
+        return null;
+    }
+
+    @Override
+    public Object getObject(final Map<String, Class<?>> map) throws SQLException {
+        return null;
+    }
+
+    @Override
+    public void setObject(final Object value) throws SQLException {
+        // no-op
+    }
+
+}
+
+/**
+ * Test cases for {@code SqlNullCheckedResultSet} class.
+ */
+public class SqlNullCheckedResultSetTest extends BaseTestCase {
+
+    private static ResultSet rs;
+
+    private static void assertArrayEquals(final byte[] expected, final byte[] actual) {
+        if (expected == actual) {
+            return;
+        }
+        if (expected.length != actual.length) {
+            assertEquals(Arrays.toString(expected), Arrays.toString(actual));
+        }
+        for (int i = 0; i < expected.length; i++) {
+            final byte expectedItem = expected[i];
+            final byte actualItem = actual[i];
+            assertEquals(expectedItem, actualItem, "Array not equal at index " + i);
+        }
+    }
+    private SqlNullCheckedResultSet rs2;
+
+    /**
+     * Sets up instance variables required by this test case.
+     */
+    @BeforeEach
+    public void setUp() throws Exception {
+        super.setUp();
+
+        rs2 = new SqlNullCheckedResultSet(ProxyFactory.instance().createResultSet(new SqlNullUncheckedMockResultSet()));
+
+        setResultSet(ProxyFactory.instance().createResultSet(rs2)); // Override superclass field
+        rs = getResultSet();
+    }
+
+    /**
+     * Tests the getAsciiStream implementation.
+     */
+    @Test
+    public void testGetAsciiStream() throws SQLException {
+
+        assertNull(rs.getAsciiStream(1));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getAsciiStream("column"));
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final InputStream stream = new ByteArrayInputStream(new byte[0]);
+        rs2.setNullAsciiStream(stream);
+        assertNotNull(rs.getAsciiStream(1));
+        assertEquals(stream, rs.getAsciiStream(1));
+        assertNotNull(rs.getAsciiStream("column"));
+        assertEquals(stream, rs.getAsciiStream("column"));
+
+    }
+
+    /**
+     * Tests the getBigDecimal implementation.
+     */
+    @Test
+    public void testGetBigDecimal() throws SQLException {
+
+        assertNull(rs.getBigDecimal(1));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getBigDecimal("column"));
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final BigDecimal bd = new BigDecimal(5.0);
+        rs2.setNullBigDecimal(bd);
+        assertNotNull(rs.getBigDecimal(1));
+        assertEquals(bd, rs.getBigDecimal(1));
+        assertNotNull(rs.getBigDecimal("column"));
+        assertEquals(bd, rs.getBigDecimal("column"));
+
+    }
+
+    /**
+     * Tests the getBinaryStream implementation.
+     */
+    @Test
+    public void testGetBinaryStream() throws SQLException {
+
+        assertNull(rs.getBinaryStream(1));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getBinaryStream("column"));
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final InputStream stream = new ByteArrayInputStream(new byte[0]);
+        rs2.setNullBinaryStream(stream);
+        assertNotNull(rs.getBinaryStream(1));
+        assertEquals(stream, rs.getBinaryStream(1));
+        assertNotNull(rs.getBinaryStream("column"));
+        assertEquals(stream, rs.getBinaryStream("column"));
+
+    }
+
+    /**
+     * Tests the getBlob implementation.
+     */
+    @Test
+    public void testGetBlob() throws SQLException {
+
+        assertNull(rs.getBlob(1));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getBlob("column"));
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final Blob blob = new SqlNullCheckedResultSetMockBlob();
+        rs2.setNullBlob(blob);
+        assertNotNull(rs.getBlob(1));
+        assertEquals(blob, rs.getBlob(1));
+        assertNotNull(rs.getBlob("column"));
+        assertEquals(blob, rs.getBlob("column"));
+
+    }
+
+    /**
+     * Tests the getBoolean implementation.
+     */
+    @Test
+    public void testGetBoolean() throws SQLException {
+
+        assertFalse(rs.getBoolean(1));
+        assertTrue(rs.wasNull());
+        assertFalse(rs.getBoolean("column"));
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        rs2.setNullBoolean(true);
+        assertTrue( rs.getBoolean(1));
+        assertTrue( rs.getBoolean("column"));
+
+    }
+
+    /**
+     * Tests the getByte implementation.
+     */
+    @Test
+    public void testGetByte() throws SQLException {
+
+        assertEquals((byte) 0, rs.getByte(1));
+        assertTrue(rs.wasNull());
+        assertEquals((byte) 0, rs.getByte("column"));
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final byte b = (byte) 10;
+        rs2.setNullByte(b);
+        assertEquals(b, rs.getByte(1));
+        assertEquals(b, rs.getByte("column"));
+
+    }
+
+    /**
+     * Tests the getByte implementation.
+     */
+    @Test
+    public void testGetBytes() throws SQLException {
+
+        assertNull(rs.getBytes(1));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getBytes("column"));
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final byte[] b = new byte[5];
+        for (int i = 0; i < 5; i++) {
+            b[0] = (byte) i;
+        }
+        rs2.setNullBytes(b);
+        assertNotNull(rs.getBytes(1));
+        assertArrayEquals(b, rs.getBytes(1));
+        assertNotNull(rs.getBytes("column"));
+        assertArrayEquals(b, rs.getBytes("column"));
+
+    }
+
+    /**
+     * Tests the getCharacterStream implementation.
+     */
+    @Test
+    public void testGetCharacterStream() throws SQLException {
+
+        assertNull(rs.getCharacterStream(1));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getCharacterStream("column"));
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final Reader reader = new CharArrayReader("this is a string".toCharArray());
+        rs2.setNullCharacterStream(reader);
+        assertNotNull(rs.getCharacterStream(1));
+        assertEquals(reader, rs.getCharacterStream(1));
+        assertNotNull(rs.getCharacterStream("column"));
+        assertEquals(reader, rs.getCharacterStream("column"));
+
+    }
+
+    /**
+     * Tests the getClob implementation.
+     */
+    @Test
+    public void testGetClob() throws SQLException {
+
+        assertNull(rs.getClob(1));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getClob("column"));
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final Clob clob = new SqlNullCheckedResultSetMockClob();
+        rs2.setNullClob(clob);
+        assertNotNull(rs.getClob(1));
+        assertEquals(clob, rs.getClob(1));
+        assertNotNull(rs.getClob("column"));
+        assertEquals(clob, rs.getClob("column"));
+
+    }
+
+    /**
+     * Tests the getDate implementation.
+     */
+    @Test
+    public void testGetDate() throws SQLException {
+
+        assertNull(rs.getDate(1));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getDate("column"));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getDate(1, Calendar.getInstance()));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getDate("column", Calendar.getInstance()));
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final java.sql.Date date = new java.sql.Date(new java.util.Date().getTime());
+        rs2.setNullDate(date);
+        assertNotNull(rs.getDate(1));
+        assertEquals(date, rs.getDate(1));
+        assertNotNull(rs.getDate("column"));
+        assertEquals(date, rs.getDate("column"));
+        assertNotNull(rs.getDate(1, Calendar.getInstance()));
+        assertEquals(date, rs.getDate(1, Calendar.getInstance()));
+        assertNotNull(rs.getDate("column", Calendar.getInstance()));
+        assertEquals(date, rs.getDate("column", Calendar.getInstance()));
+
+    }
+
+    /**
+     * Tests the getDouble implementation.
+     */
+    @Test
+    public void testGetDouble() throws SQLException {
+
+        assertEquals(0.0, rs.getDouble(1), 0.0);
+        assertTrue(rs.wasNull());
+        assertEquals(0.0, rs.getDouble("column"), 0.0);
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final double d = 10.0;
+        rs2.setNullDouble(d);
+        assertEquals(d, rs.getDouble(1), 0.0);
+        assertEquals(d, rs.getDouble("column"), 0.0);
+
+    }
+
+    /**
+     * Tests the getFloat implementation.
+     */
+    @Test
+    public void testGetFloat() throws SQLException {
+        assertEquals(0, rs.getFloat(1), 0.0);
+        assertTrue(rs.wasNull());
+        assertEquals(0, rs.getFloat("column"), 0.0);
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final float f = 10;
+        rs2.setNullFloat(f);
+        assertEquals(f, rs.getFloat(1), 0.0);
+        assertEquals(f, rs.getFloat("column"), 0.0);
+    }
+
+    /**
+     * Tests the getInt implementation.
+     */
+    @Test
+    public void testGetInt() throws SQLException {
+        assertEquals(0, rs.getInt(1));
+        assertTrue(rs.wasNull());
+        assertEquals(0, rs.getInt("column"));
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final int i = 10;
+        rs2.setNullInt(i);
+        assertEquals(i, rs.getInt(1));
+        assertEquals(i, rs.getInt("column"));
+    }
+
+    /**
+     * Tests the getLong implementation.
+     */
+    @Test
+    public void testGetLong() throws SQLException {
+        assertEquals(0, rs.getLong(1));
+        assertTrue(rs.wasNull());
+        assertEquals(0, rs.getLong("column"));
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final long l = 10;
+        rs2.setNullLong(l);
+        assertEquals(l, rs.getLong(1));
+        assertEquals(l, rs.getLong("column"));
+    }
+
+    /**
+     * Tests the getObject implementation.
+     */
+    @Test
+    public void testGetObject() throws SQLException {
+
+        assertNull(rs.getObject(1));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getObject("column"));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getObject(1, (Map<String, Class<?>>) null));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getObject("column", (Map<String, Class<?>>) null));
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final Object o = new Object();
+        rs2.setNullObject(o);
+        assertNotNull(rs.getObject(1));
+        assertEquals(o, rs.getObject(1));
+        assertNotNull(rs.getObject("column"));
+        assertEquals(o, rs.getObject("column"));
+        assertNotNull(rs.getObject(1, (Map<String, Class<?>>) null));
+        assertEquals(o, rs.getObject(1, (Map<String, Class<?>>) null));
+        assertNotNull(rs.getObject("column", (Map<String, Class<?>>) null));
+        assertEquals(o, rs.getObject("column", (Map<String, Class<?>>) null));
+
+    }
+
+    /**
+     * Tests the getRef implementation.
+     */
+    @Test
+    public void testGetRef() throws SQLException {
+
+        assertNull(rs.getRef(1));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getRef("column"));
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final Ref ref = new SqlNullCheckedResultSetMockRef();
+        rs2.setNullRef(ref);
+        assertNotNull(rs.getRef(1));
+        assertEquals(ref, rs.getRef(1));
+        assertNotNull(rs.getRef("column"));
+        assertEquals(ref, rs.getRef("column"));
+
+    }
+
+    /**
+     * Tests the getShort implementation.
+     */
+    @Test
+    public void testGetShort() throws SQLException {
+
+        assertEquals((short) 0, rs.getShort(1));
+        assertTrue(rs.wasNull());
+        assertEquals((short) 0, rs.getShort("column"));
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final short s = (short) 10;
+        rs2.setNullShort(s);
+        assertEquals(s, rs.getShort(1));
+        assertEquals(s, rs.getShort("column"));
+    }
+
+    /**
+     * Tests the getString implementation.
+     */
+    @Test
+    public void testGetString() throws SQLException {
+        assertNull(rs.getString(1));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getString("column"));
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final String s = "hello, world";
+        rs2.setNullString(s);
+        assertEquals(s, rs.getString(1));
+        assertEquals(s, rs.getString("column"));
+    }
+
+    /**
+     * Tests the getTime implementation.
+     */
+    @Test
+    public void testGetTime() throws SQLException {
+
+        assertNull(rs.getTime(1));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getTime("column"));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getTime(1, Calendar.getInstance()));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getTime("column", Calendar.getInstance()));
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final Time time = new Time(new java.util.Date().getTime());
+        rs2.setNullTime(time);
+        assertNotNull(rs.getTime(1));
+        assertEquals(time, rs.getTime(1));
+        assertNotNull(rs.getTime("column"));
+        assertEquals(time, rs.getTime("column"));
+        assertNotNull(rs.getTime(1, Calendar.getInstance()));
+        assertEquals(time, rs.getTime(1, Calendar.getInstance()));
+        assertNotNull(rs.getTime("column", Calendar.getInstance()));
+        assertEquals(time, rs.getTime("column", Calendar.getInstance()));
+
+    }
+
+    /**
+     * Tests the getTimestamp implementation.
+     */
+    @Test
+    public void testGetTimestamp() throws SQLException {
+
+        assertNull(rs.getTimestamp(1));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getTimestamp("column"));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getTimestamp(1, Calendar.getInstance()));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getTimestamp("column", Calendar.getInstance()));
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final Timestamp ts = new Timestamp(new java.util.Date().getTime());
+        rs2.setNullTimestamp(ts);
+        assertNotNull(rs.getTimestamp(1));
+        assertEquals(ts, rs.getTimestamp(1));
+        assertNotNull(rs.getTimestamp("column"));
+        assertEquals(ts, rs.getTimestamp("column"));
+        assertNotNull(rs.getTimestamp(1, Calendar.getInstance()));
+        assertEquals(ts, rs.getTimestamp(1, Calendar.getInstance()));
+        assertNotNull(rs.getTimestamp("column", Calendar.getInstance()));
+        assertEquals(ts, rs.getTimestamp("column", Calendar.getInstance()));
+    }
+
+    /**
+     * Tests the setNullAsciiStream implementation.
+     */
+    @Test
+    public void testSetNullAsciiStream() throws SQLException {
+
+        assertNull(rs2.getNullAsciiStream());
+        // Set what gets returned to something other than the default
+        final InputStream stream = new ByteArrayInputStream(new byte[0]);
+        rs2.setNullAsciiStream(stream);
+        assertNotNull(rs.getAsciiStream(1));
+        assertEquals(stream, rs.getAsciiStream(1));
+        assertNotNull(rs.getAsciiStream("column"));
+        assertEquals(stream, rs.getAsciiStream("column"));
+
+    }
+
+    /**
+     * Tests the setNullBigDecimal implementation.
+     */
+    @Test
+    public void testSetNullBigDecimal() throws SQLException {
+
+        assertNull(rs2.getNullBigDecimal());
+        // Set what gets returned to something other than the default
+        final BigDecimal bd = new BigDecimal(5.0);
+        rs2.setNullBigDecimal(bd);
+        assertNotNull(rs.getBigDecimal(1));
+        assertEquals(bd, rs.getBigDecimal(1));
+        assertNotNull(rs.getBigDecimal("column"));
+        assertEquals(bd, rs.getBigDecimal("column"));
+
+    }
+
+    /**
+     * Tests the setNullBinaryStream implementation.
+     */
+    @Test
+    public void testSetNullBinaryStream() throws SQLException {
+
+        assertNull(rs2.getNullBinaryStream());
+        // Set what gets returned to something other than the default
+        final InputStream stream = new ByteArrayInputStream(new byte[0]);
+        rs2.setNullBinaryStream(stream);
+        assertNotNull(rs.getBinaryStream(1));
+        assertEquals(stream, rs.getBinaryStream(1));
+        assertNotNull(rs.getBinaryStream("column"));
+        assertEquals(stream, rs.getBinaryStream("column"));
+
+    }
+
+    /**
+     * Tests the setNullBlob implementation.
+     */
+    @Test
+    public void testSetNullBlob() throws SQLException {
+
+        assertNull(rs2.getNullBlob());
+        // Set what gets returned to something other than the default
+        final Blob blob = new SqlNullCheckedResultSetMockBlob();
+        rs2.setNullBlob(blob);
+        assertNotNull(rs.getBlob(1));
+        assertEquals(blob, rs.getBlob(1));
+        assertNotNull(rs.getBlob("column"));
+        assertEquals(blob, rs.getBlob("column"));
+
+    }
+
+    /**
+     * Tests the setNullBoolean implementation.
+     */
+    @Test
+    public void testSetNullBoolean() throws SQLException {
+
+        assertFalse(rs2.getNullBoolean());
+        // Set what gets returned to something other than the default
+        rs2.setNullBoolean(true);
+        assertTrue( rs.getBoolean(1));
+        assertTrue( rs.getBoolean("column"));
+
+    }
+
+    /**
+     * Tests the setNullByte implementation.
+     */
+    @Test
+    public void testSetNullByte() throws SQLException {
+
+        assertEquals((byte) 0, rs2.getNullByte());
+        // Set what gets returned to something other than the default
+        final byte b = (byte) 10;
+        rs2.setNullByte(b);
+        assertEquals(b, rs.getByte(1));
+        assertEquals(b, rs.getByte("column"));
+
+    }
+
+    /**
+     * Tests the setNullByte implementation.
+     */
+    @Test
+    public void testSetNullBytes() throws SQLException {
+        // test the default, unset value
+        assertNull(rs2.getNullBytes());
+
+        // test that setting null is safe
+        rs2.setNullBytes(null);
+        assertNull(rs2.getNullBytes());
+
+        // Set what gets returned to something other than the default
+        final byte[] b = new byte[5];
+        for (int i = 0; i < 5; i++) {
+            b[0] = (byte) i;
+        }
+        rs2.setNullBytes(b);
+        assertNotNull(rs.getBytes(1));
+        assertArrayEquals(b, rs.getBytes(1));
+        assertNotNull(rs.getBytes("column"));
+        assertArrayEquals(b, rs.getBytes("column"));
+
+    }
+
+    /**
+     * Tests the setNullCharacterStream implementation.
+     */
+    @Test
+    public void testSetNullCharacterStream() throws SQLException {
+
+        assertNull(rs2.getNullCharacterStream());
+        // Set what gets returned to something other than the default
+        final Reader reader = new CharArrayReader("this is a string".toCharArray());
+        rs2.setNullCharacterStream(reader);
+        assertNotNull(rs.getCharacterStream(1));
+        assertEquals(reader, rs.getCharacterStream(1));
+        assertNotNull(rs.getCharacterStream("column"));
+        assertEquals(reader, rs.getCharacterStream("column"));
+
+    }
+
+    /**
+     * Tests the setNullClob implementation.
+     */
+    @Test
+    public void testSetNullClob() throws SQLException {
+
+        assertNull(rs2.getNullClob());
+        // Set what gets returned to something other than the default
+        final Clob clob = new SqlNullCheckedResultSetMockClob();
+        rs2.setNullClob(clob);
+        assertNotNull(rs.getClob(1));
+        assertEquals(clob, rs.getClob(1));
+        assertNotNull(rs.getClob("column"));
+        assertEquals(clob, rs.getClob("column"));
+
+    }
+
+    /**
+     * Tests the setNullDate implementation.
+     */
+    @Test
+    public void testSetNullDate() throws SQLException {
+        // test the default, unset value
+        assertNull(rs2.getNullDate());
+
+        // test that setting null is safe
+        rs2.setNullDate(null);
+        assertNull(rs2.getNullDate());
+
+        // Set what gets returned to something other than the default
+        final java.sql.Date date = new java.sql.Date(new java.util.Date().getTime());
+        rs2.setNullDate(date);
+        assertNotNull(rs.getDate(1));
+        assertEquals(date, rs.getDate(1));
+        assertNotNull(rs.getDate("column"));
+        assertEquals(date, rs.getDate("column"));
+        assertNotNull(rs.getDate(1, Calendar.getInstance()));
+        assertEquals(date, rs.getDate(1, Calendar.getInstance()));
+        assertNotNull(rs.getDate("column", Calendar.getInstance()));
+        assertEquals(date, rs.getDate("column", Calendar.getInstance()));
+
+    }
+
+    /**
+     * Tests the setNullDouble implementation.
+     */
+    @Test
+    public void testSetNullDouble() throws SQLException {
+        assertEquals(0.0, rs2.getNullDouble(), 0.0);
+        // Set what gets returned to something other than the default
+        final double d = 10.0;
+        rs2.setNullDouble(d);
+        assertEquals(d, rs.getDouble(1), 0.0);
+        assertEquals(d, rs.getDouble("column"), 0.0);
+    }
+
+    /**
+     * Tests the setNullFloat implementation.
+     */
+    @Test
+    public void testSetNullFloat() throws SQLException {
+        assertEquals((float) 0.0, rs2.getNullFloat(), 0.0);
+        // Set what gets returned to something other than the default
+        final float f = (float) 10.0;
+        rs2.setNullFloat(f);
+        assertEquals(f, rs.getFloat(1), 0.0);
+        assertEquals(f, rs.getFloat("column"), 0.0);
+    }
+
+    /**
+     * Tests the setNullInt implementation.
+     */
+    @Test
+    public void testSetNullInt() throws SQLException {
+        assertEquals(0, rs2.getNullInt());
+        assertEquals(0, rs.getInt(1));
+        assertTrue(rs.wasNull());
+        assertEquals(0, rs.getInt("column"));
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final int i = 10;
+        rs2.setNullInt(i);
+        assertEquals(i, rs.getInt(1));
+        assertEquals(i, rs.getInt("column"));
+    }
+
+    /**
+     * Tests the setNullLong implementation.
+     */
+    @Test
+    public void testSetNullLong() throws SQLException {
+        assertEquals(0, rs2.getNullLong());
+        // Set what gets returned to something other than the default
+        final long l = 10;
+        rs2.setNullLong(l);
+        assertEquals(l, rs.getLong(1));
+        assertEquals(l, rs.getLong("column"));
+    }
+
+    /**
+     * Tests the setNullObject implementation.
+     */
+    @Test
+    public void testSetNullObject() throws SQLException {
+        assertNull(rs2.getNullObject());
+        // Set what gets returned to something other than the default
+        final Object o = new Object();
+        rs2.setNullObject(o);
+        assertNotNull(rs.getObject(1));
+        assertEquals(o, rs.getObject(1));
+        assertNotNull(rs.getObject("column"));
+        assertEquals(o, rs.getObject("column"));
+        assertNotNull(rs.getObject(1, (Map<String, Class<?>>) null));
+        assertEquals(o, rs.getObject(1, (Map<String, Class<?>>) null));
+        assertNotNull(rs.getObject("column", (Map<String, Class<?>>) null));
+        assertEquals(o, rs.getObject("column", (Map<String, Class<?>>) null));
+    }
+
+    /**
+     * Tests the setNullRef implementation.
+     */
+    @Test
+    public void testSetNullRef() throws SQLException {
+        assertNull(rs2.getNullRef());
+        // Set what gets returned to something other than the default
+        final Ref ref = new SqlNullCheckedResultSetMockRef();
+        rs2.setNullRef(ref);
+        assertNotNull(rs.getRef(1));
+        assertEquals(ref, rs.getRef(1));
+        assertNotNull(rs.getRef("column"));
+        assertEquals(ref, rs.getRef("column"));
+    }
+
+    /**
+     * Tests the setNullShort implementation.
+     */
+    @Test
+    public void testSetNullShort() throws SQLException {
+
+        assertEquals((short) 0, rs2.getNullShort());
+        // Set what gets returned to something other than the default
+        final short s = (short) 10;
+        rs2.setNullShort(s);
+        assertEquals(s, rs.getShort(1));
+        assertEquals(s, rs.getShort("column"));
+
+    }
+
+    /**
+     * Tests the setNullString implementation.
+     */
+    @Test
+    public void testSetNullString() throws SQLException {
+        assertNull(rs2.getNullString());
+        // Set what gets returned to something other than the default
+        final String s = "hello, world";
+        rs2.setNullString(s);
+        assertEquals(s, rs.getString(1));
+        assertEquals(s, rs.getString("column"));
+    }
+
+    /**
+     * Tests the setNullTime implementation.
+     */
+    @Test
+    public void testSetNullTime() throws SQLException {
+        // test the default, unset value
+        assertNull(rs2.getNullTime());
+
+        // test that setting null is safe
+        rs2.setNullTime(null);
+        assertNull(rs2.getNullTime());
+
+        // Set what gets returned to something other than the default
+        final Time time = new Time(new java.util.Date().getTime());
+        rs2.setNullTime(time);
+        assertNotNull(rs.getTime(1));
+        assertEquals(time, rs.getTime(1));
+        assertNotNull(rs.getTime("column"));
+        assertEquals(time, rs.getTime("column"));
+        assertNotNull(rs.getTime(1, Calendar.getInstance()));
+        assertEquals(time, rs.getTime(1, Calendar.getInstance()));
+        assertNotNull(rs.getTime("column", Calendar.getInstance()));
+        assertEquals(time, rs.getTime("column", Calendar.getInstance()));
+    }
+
+    /**
+     * Tests the setNullTimestamp implementation.
+     */
+    @Test
+    public void testSetNullTimestamp() throws SQLException {
+        // test the default, unset value
+        assertNull(rs2.getNullTimestamp());
+
+        // test that setting null is safe
+        rs2.setNullTimestamp(null);
+        assertNull(rs2.getNullTimestamp());
+
+        // Set what gets returned to something other than the default
+        final Timestamp ts = new Timestamp(new java.util.Date().getTime());
+        rs2.setNullTimestamp(ts);
+        assertNotNull(rs.getTimestamp(1));
+        assertEquals(ts, rs.getTimestamp(1));
+        assertNotNull(rs.getTimestamp("column"));
+        assertEquals(ts, rs.getTimestamp("column"));
+        assertNotNull(rs.getTimestamp(1, Calendar.getInstance()));
+        assertEquals(ts, rs.getTimestamp(1, Calendar.getInstance()));
+        assertNotNull(rs.getTimestamp("column", Calendar.getInstance()));
+        assertEquals(ts, rs.getTimestamp("column", Calendar.getInstance()));
+    }
+
+    /**
+     * Tests the getURL and setNullURL implementations.
+     */
+    @Test
+    public void testURL() throws SQLException, MalformedURLException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+        assertNull(rs.getURL(1));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getURL("column"));
+        assertTrue(rs.wasNull());
+        // Set what gets returned to something other than the default
+        final URL u = new URL("http://www.apache.org");
+        rs2.setNullURL(u);
+        assertEquals(u, rs.getURL(1));
+        assertEquals(u, rs.getURL("column"));
+    }
+
+    @Test
+    public void testWrapResultSet() throws SQLException {
+        final ResultSet wrappedRs = mock(ResultSet.class);
+        final ResultSet rs = SqlNullCheckedResultSet.wrap(wrappedRs);
+        rs.beforeFirst();
+        verify(wrappedRs).beforeFirst();
+        rs.next();
+        verify(wrappedRs).next();
+    }
+}
+
+final class SqlNullUncheckedMockResultSet implements InvocationHandler {
+
+    /**
+     * Always return false for booleans, 0 for numerics, and null for Objects.
+     *
+     * @see java.lang.reflect.InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])
+     */
+    @Override
+    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
+
+        final Class<?> returnType = method.getReturnType();
+
+        if (method.getName().equals("wasNull")) {
+            return Boolean.TRUE;
+
+        }
+        if (returnType.equals(Boolean.TYPE)) {
+            return Boolean.FALSE;
+
+        }
+        if (returnType.equals(Integer.TYPE)) {
+            return Integer.valueOf(0);
+
+        }
+        if (returnType.equals(Short.TYPE)) {
+            return Short.valueOf((short) 0);
+
+        }
+        if (returnType.equals(Double.TYPE)) {
+            return Double.valueOf(0);
+
+        }
+        if (returnType.equals(Long.TYPE)) {
+            return Long.valueOf(0);
+
+        }
+        if (returnType.equals(Byte.TYPE)) {
+            return Byte.valueOf((byte) 0);
+
+        }
+        if (returnType.equals(Float.TYPE)) {
+            return Float.valueOf(0);
+
+        }
+        return null;
+    }
+}