/* | |
* 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 = false; | |
/** | |
* 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 rs | |
* ResultSet to close | |
* @throws SQLException | |
* if a database access error occurs | |
* @since 1.1 | |
*/ | |
protected void close(final ResultSet rs) throws SQLException { | |
DbUtils.close(rs); | |
} | |
/** | |
* 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 rs ResultSet to close. | |
* @since 1.8.0 | |
*/ | |
protected void closeQuietly(final ResultSet rs) { | |
DbUtils.closeQuietly(rs); | |
} | |
/** | |
* 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 = this.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 RuntimeException("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 RuntimeException("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; | |
} | |
/** | |
* Get 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 (this.getDataSource() == null) { | |
throw new SQLException( | |
"QueryRunner requires a DataSource to be " | |
+ "invoked in this way, or a Connection should be passed in"); | |
} | |
return this.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; | |
} | |
} |