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 & 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 & 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 & 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 & 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: - * <br/> - * <ol> - * <li> - * The class has a writable property with the same name as a column. - * The name comparison is case insensitive. - * </li> - * - * <li> - * 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. - * </li> - * </ol> - * - * <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. - * </p> - * @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: - * <br/> - * <ol> - * <li> - * The class has a writable property with the same name as a column. - * The name comparison is case insensitive. - * </li> - * - * <li> - * 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. - * </li> - * </ol> - * - * <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. - * </p> - * @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: + * <br/> + * <ol> + * <li> + * The class has a writable property with the same name as a column. + * The name comparison is case insensitive. + * </li> + * + * <li> + * 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. + * </li> + * </ol> + * + * <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. + * </p> + * @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: + * <br/> + * <ol> + * <li> + * The class has a writable property with the same name as a column. + * The name comparison is case insensitive. + * </li> + * + * <li> + * 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. + * </li> + * </ol> + * + * <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. + * </p> + * @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<Object[]>}. 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<Object[]>}. 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<Map<Long, Person>> h = new BeanMapHandler<Long, Person>(Person.class, "id"); - * Map<Long, Person> found = queryRunner.query("select id, name, age from person", 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. - * </p> - * <p> - * This class is thread safe. - * </p> - * - * @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<Map<Long, Person>> h = new BeanMapHandler<Long, Person>(Person.class, "id"); + * Map<Long, Person> found = queryRunner.query("select id, name, age from person", 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. + * </p> + * <p> + * This class is thread safe. + * </p> + * + * @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. - * </p> - * <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. + * </p> + * <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> - * </p> - * <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> + * </p> + * <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; + } +}