AbstractQueryRunner.java
/*
* 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 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 = 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 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;
}
/**
* 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;
}
}