0001: /*
0002: * Copyright 2002-2007 the original author or authors.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016:
0017: package org.springframework.jdbc.core;
0018:
0019: import java.lang.reflect.InvocationHandler;
0020: import java.lang.reflect.InvocationTargetException;
0021: import java.lang.reflect.Method;
0022: import java.lang.reflect.Proxy;
0023: import java.sql.CallableStatement;
0024: import java.sql.Connection;
0025: import java.sql.PreparedStatement;
0026: import java.sql.ResultSet;
0027: import java.sql.SQLException;
0028: import java.sql.SQLWarning;
0029: import java.sql.Statement;
0030: import java.util.ArrayList;
0031: import java.util.HashMap;
0032: import java.util.List;
0033: import java.util.Map;
0034:
0035: import javax.sql.DataSource;
0036:
0037: import org.springframework.dao.DataAccessException;
0038: import org.springframework.dao.InvalidDataAccessApiUsageException;
0039: import org.springframework.dao.support.DataAccessUtils;
0040: import org.springframework.jdbc.SQLWarningException;
0041: import org.springframework.jdbc.datasource.ConnectionProxy;
0042: import org.springframework.jdbc.datasource.DataSourceUtils;
0043: import org.springframework.jdbc.support.JdbcAccessor;
0044: import org.springframework.jdbc.support.JdbcUtils;
0045: import org.springframework.jdbc.support.KeyHolder;
0046: import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor;
0047: import org.springframework.jdbc.support.rowset.SqlRowSet;
0048: import org.springframework.util.Assert;
0049: import org.springframework.core.CollectionFactory;
0050:
0051: /**
0052: * <b>This is the central class in the JDBC core package.</b>
0053: * It simplifies the use of JDBC and helps to avoid common errors.
0054: * It executes core JDBC workflow, leaving application code to provide SQL
0055: * and extract results. This class executes SQL queries or updates, initiating
0056: * iteration over ResultSets and catching JDBC exceptions and translating
0057: * them to the generic, more informative exception hierarchy defined in the
0058: * <code>org.springframework.dao</code> package.
0059: *
0060: * <p>Code using this class need only implement callback interfaces, giving
0061: * them a clearly defined contract. The {@link PreparedStatementCreator} callback
0062: * interface creates a prepared statement given a Connection, providing SQL and
0063: * any necessary parameters. The {@link ResultSetExtractor} interface extracts
0064: * values from a ResultSet. See also {@link PreparedStatementSetter} and
0065: * {@link RowMapper} for two popular alternative callback interfaces.
0066: *
0067: * <p>Can be used within a service implementation via direct instantiation
0068: * with a DataSource reference, or get prepared in an application context
0069: * and given to services as bean reference. Note: The DataSource should
0070: * always be configured as a bean in the application context, in the first case
0071: * given to the service directly, in the second case to the prepared template.
0072: *
0073: * <p>Because this class is parameterizable by the callback interfaces and
0074: * the {@link org.springframework.jdbc.support.SQLExceptionTranslator}
0075: * interface, there should be no need to subclass it.
0076: *
0077: * <p>All SQL operations performed by this class are logged at debug level,
0078: * using "org.springframework.jdbc.core.JdbcTemplate" as log category.
0079: *
0080: * @author Rod Johnson
0081: * @author Juergen Hoeller
0082: * @author Thomas Risberg
0083: * @since May 3, 2001
0084: * @see PreparedStatementCreator
0085: * @see PreparedStatementSetter
0086: * @see CallableStatementCreator
0087: * @see PreparedStatementCallback
0088: * @see CallableStatementCallback
0089: * @see ResultSetExtractor
0090: * @see RowCallbackHandler
0091: * @see RowMapper
0092: * @see org.springframework.jdbc.support.SQLExceptionTranslator
0093: */
0094: public class JdbcTemplate extends JdbcAccessor implements
0095: JdbcOperations {
0096:
0097: private static final String RETURN_RESULT_SET_PREFIX = "#result-set-";
0098:
0099: private static final String RETURN_UPDATE_COUNT_PREFIX = "#update-count-";
0100:
0101: /** Custom NativeJdbcExtractor */
0102: private NativeJdbcExtractor nativeJdbcExtractor;
0103:
0104: /** If this variable is false, we will throw exceptions on SQL warnings */
0105: private boolean ignoreWarnings = true;
0106:
0107: /**
0108: * If this variable is set to a non-zero value, it will be used for setting the
0109: * fetchSize property on statements used for query processing.
0110: */
0111: private int fetchSize = 0;
0112:
0113: /**
0114: * If this variable is set to a non-zero value, it will be used for setting the
0115: * maxRows property on statements used for query processing.
0116: */
0117: private int maxRows = 0;
0118:
0119: /**
0120: * If this variable is set to a non-zero value, it will be used for setting the
0121: * queryTimeout property on statements used for query processing.
0122: */
0123: private int queryTimeout = 0;
0124:
0125: /**
0126: * If this variable is set to true then all results checking will be bypassed for any
0127: * callable statement processing. This can be used to avoid a bug in some older Oracle
0128: * JDBC drivers like 10.1.0.2.
0129: */
0130: private boolean skipResultsProcessing = false;
0131:
0132: /**
0133: * If this variable is set to true then execution of a CallableStatement will return the results in a Map
0134: * that uses case insensitive names for the parameters if Commons Collections are available on the classpath.
0135: */
0136: private boolean resultsMapCaseInsensitive = false;
0137:
0138: /**
0139: * Construct a new JdbcTemplate for bean usage.
0140: * <p>Note: The DataSource has to be set before using the instance.
0141: * @see #setDataSource
0142: */
0143: public JdbcTemplate() {
0144: }
0145:
0146: /**
0147: * Construct a new JdbcTemplate, given a DataSource to obtain connections from.
0148: * <p>Note: This will not trigger initialization of the exception translator.
0149: * @param dataSource the JDBC DataSource to obtain connections from
0150: */
0151: public JdbcTemplate(DataSource dataSource) {
0152: setDataSource(dataSource);
0153: afterPropertiesSet();
0154: }
0155:
0156: /**
0157: * Construct a new JdbcTemplate, given a DataSource to obtain connections from.
0158: * <p>Note: Depending on the "lazyInit" flag, initialization of the exception translator
0159: * will be triggered.
0160: * @param dataSource the JDBC DataSource to obtain connections from
0161: * @param lazyInit whether to lazily initialize the SQLExceptionTranslator
0162: */
0163: public JdbcTemplate(DataSource dataSource, boolean lazyInit) {
0164: setDataSource(dataSource);
0165: setLazyInit(lazyInit);
0166: afterPropertiesSet();
0167: }
0168:
0169: /**
0170: * Set a NativeJdbcExtractor to extract native JDBC objects from wrapped handles.
0171: * Useful if native Statement and/or ResultSet handles are expected for casting
0172: * to database-specific implementation classes, but a connection pool that wraps
0173: * JDBC objects is used (note: <i>any</i> pool will return wrapped Connections).
0174: */
0175: public void setNativeJdbcExtractor(NativeJdbcExtractor extractor) {
0176: this .nativeJdbcExtractor = extractor;
0177: }
0178:
0179: /**
0180: * Return the current NativeJdbcExtractor implementation.
0181: */
0182: public NativeJdbcExtractor getNativeJdbcExtractor() {
0183: return this .nativeJdbcExtractor;
0184: }
0185:
0186: /**
0187: * Set whether or not we want to ignore SQLWarnings.
0188: * <p>Default is "true", swallowing and logging all warnings. Switch this flag
0189: * to "false" to make the JdbcTemplate throw a SQLWarningException instead.
0190: * @see java.sql.SQLWarning
0191: * @see org.springframework.jdbc.SQLWarningException
0192: * @see #handleWarnings
0193: */
0194: public void setIgnoreWarnings(boolean ignoreWarnings) {
0195: this .ignoreWarnings = ignoreWarnings;
0196: }
0197:
0198: /**
0199: * Return whether or not we ignore SQLWarnings.
0200: */
0201: public boolean isIgnoreWarnings() {
0202: return this .ignoreWarnings;
0203: }
0204:
0205: /**
0206: * Set the fetch size for this JdbcTemplate. This is important for processing
0207: * large result sets: Setting this higher than the default value will increase
0208: * processing speed at the cost of memory consumption; setting this lower can
0209: * avoid transferring row data that will never be read by the application.
0210: * <p>Default is 0, indicating to use the JDBC driver's default.
0211: * @see java.sql.Statement#setFetchSize
0212: */
0213: public void setFetchSize(int fetchSize) {
0214: this .fetchSize = fetchSize;
0215: }
0216:
0217: /**
0218: * Return the fetch size specified for this JdbcTemplate.
0219: */
0220: public int getFetchSize() {
0221: return this .fetchSize;
0222: }
0223:
0224: /**
0225: * Set the maximum number of rows for this JdbcTemplate. This is important
0226: * for processing subsets of large result sets, avoiding to read and hold
0227: * the entire result set in the database or in the JDBC driver if we're
0228: * never interested in the entire result in the first place (for example,
0229: * when performing searches that might return a large number of matches).
0230: * <p>Default is 0, indicating to use the JDBC driver's default.
0231: * @see java.sql.Statement#setMaxRows
0232: */
0233: public void setMaxRows(int maxRows) {
0234: this .maxRows = maxRows;
0235: }
0236:
0237: /**
0238: * Return the maximum number of rows specified for this JdbcTemplate.
0239: */
0240: public int getMaxRows() {
0241: return this .maxRows;
0242: }
0243:
0244: /**
0245: * Set the query timeout for statements that this JdbcTemplate executes.
0246: * <p>Default is 0, indicating to use the JDBC driver's default.
0247: * <p>Note: Any timeout specified here will be overridden by the remaining
0248: * transaction timeout when executing within a transaction that has a
0249: * timeout specified at the transaction level.
0250: * @see java.sql.Statement#setQueryTimeout
0251: */
0252: public void setQueryTimeout(int queryTimeout) {
0253: this .queryTimeout = queryTimeout;
0254: }
0255:
0256: /**
0257: * Return the query timeout for statements that this JdbcTemplate executes.
0258: */
0259: public int getQueryTimeout() {
0260: return this .queryTimeout;
0261: }
0262:
0263: /**
0264: * Set whether results processing should be skipped. Can be used to optimize callable
0265: * statement processing when we know that no results are being passed back - the processing
0266: * of out parameter will still take place. This can be used to avoid a bug in some older
0267: * Oracle JDBC drivers like 10.1.0.2.
0268: */
0269: public void setSkipResultsProcessing(boolean skipResultsProcessing) {
0270: this .skipResultsProcessing = skipResultsProcessing;
0271: }
0272:
0273: /**
0274: * Return whether results processing should be skipped.
0275: */
0276: public boolean isSkipResultsProcessing() {
0277: return this .skipResultsProcessing;
0278: }
0279:
0280: /**
0281: * Return whether execution of a CallableStatement will return the results in a Map
0282: * that uses case insensitive names for the parameters.
0283: */
0284: public boolean isResultsMapCaseInsensitive() {
0285: return resultsMapCaseInsensitive;
0286: }
0287:
0288: /*
0289: * Set whether execution of a CallableStatement will return the results in a Map
0290: * that uses case insensitive names for the parameters.
0291: */
0292: public void setResultsMapCaseInsensitive(
0293: boolean resultsMapCaseInsensitive) {
0294: this .resultsMapCaseInsensitive = resultsMapCaseInsensitive;
0295: }
0296:
0297: //-------------------------------------------------------------------------
0298: // Methods dealing with a plain java.sql.Connection
0299: //-------------------------------------------------------------------------
0300:
0301: public Object execute(ConnectionCallback action)
0302: throws DataAccessException {
0303: Assert.notNull(action, "Callback object must not be null");
0304:
0305: Connection con = DataSourceUtils.getConnection(getDataSource());
0306: try {
0307: Connection conToUse = con;
0308: if (this .nativeJdbcExtractor != null) {
0309: // Extract native JDBC Connection, castable to OracleConnection or the like.
0310: conToUse = this .nativeJdbcExtractor
0311: .getNativeConnection(con);
0312: } else {
0313: // Create close-suppressing Connection proxy, also preparing returned Statements.
0314: conToUse = createConnectionProxy(con);
0315: }
0316: return action.doInConnection(conToUse);
0317: } catch (SQLException ex) {
0318: // Release Connection early, to avoid potential connection pool deadlock
0319: // in the case when the exception translator hasn't been initialized yet.
0320: DataSourceUtils.releaseConnection(con, getDataSource());
0321: con = null;
0322: throw getExceptionTranslator().translate(
0323: "ConnectionCallback", getSql(action), ex);
0324: } finally {
0325: DataSourceUtils.releaseConnection(con, getDataSource());
0326: }
0327: }
0328:
0329: /**
0330: * Create a close-suppressing proxy for the given JDBC Connection.
0331: * Called by the <code>execute</code> method.
0332: * <p>The proxy also prepares returned JDBC Statements, applying
0333: * statement settings such as fetch size, max rows, and query timeout.
0334: * @param con the JDBC Connection to create a proxy for
0335: * @return the Connection proxy
0336: * @see java.sql.Connection#close()
0337: * @see #execute(ConnectionCallback)
0338: * @see #applyStatementSettings
0339: */
0340: protected Connection createConnectionProxy(Connection con) {
0341: return (Connection) Proxy.newProxyInstance(
0342: ConnectionProxy.class.getClassLoader(),
0343: new Class[] { ConnectionProxy.class },
0344: new CloseSuppressingInvocationHandler(con));
0345: }
0346:
0347: //-------------------------------------------------------------------------
0348: // Methods dealing with static SQL (java.sql.Statement)
0349: //-------------------------------------------------------------------------
0350:
0351: public Object execute(StatementCallback action)
0352: throws DataAccessException {
0353: Assert.notNull(action, "Callback object must not be null");
0354:
0355: Connection con = DataSourceUtils.getConnection(getDataSource());
0356: Statement stmt = null;
0357: try {
0358: Connection conToUse = con;
0359: if (this .nativeJdbcExtractor != null
0360: && this .nativeJdbcExtractor
0361: .isNativeConnectionNecessaryForNativeStatements()) {
0362: conToUse = this .nativeJdbcExtractor
0363: .getNativeConnection(con);
0364: }
0365: stmt = conToUse.createStatement();
0366: applyStatementSettings(stmt);
0367: Statement stmtToUse = stmt;
0368: if (this .nativeJdbcExtractor != null) {
0369: stmtToUse = this .nativeJdbcExtractor
0370: .getNativeStatement(stmt);
0371: }
0372: Object result = action.doInStatement(stmtToUse);
0373: handleWarnings(stmt.getWarnings());
0374: return result;
0375: } catch (SQLException ex) {
0376: // Release Connection early, to avoid potential connection pool deadlock
0377: // in the case when the exception translator hasn't been initialized yet.
0378: JdbcUtils.closeStatement(stmt);
0379: stmt = null;
0380: DataSourceUtils.releaseConnection(con, getDataSource());
0381: con = null;
0382: throw getExceptionTranslator().translate(
0383: "StatementCallback", getSql(action), ex);
0384: } finally {
0385: JdbcUtils.closeStatement(stmt);
0386: DataSourceUtils.releaseConnection(con, getDataSource());
0387: }
0388: }
0389:
0390: public void execute(final String sql) throws DataAccessException {
0391: if (logger.isDebugEnabled()) {
0392: logger.debug("Executing SQL statement [" + sql + "]");
0393: }
0394:
0395: class ExecuteStatementCallback implements StatementCallback,
0396: SqlProvider {
0397: public Object doInStatement(Statement stmt)
0398: throws SQLException {
0399: stmt.execute(sql);
0400: return null;
0401: }
0402:
0403: public String getSql() {
0404: return sql;
0405: }
0406: }
0407: execute(new ExecuteStatementCallback());
0408: }
0409:
0410: public Object query(final String sql, final ResultSetExtractor rse)
0411: throws DataAccessException {
0412: Assert.notNull(sql, "SQL must not be null");
0413: Assert.notNull(rse, "ResultSetExtractor must not be null");
0414: if (logger.isDebugEnabled()) {
0415: logger.debug("Executing SQL query [" + sql + "]");
0416: }
0417:
0418: class QueryStatementCallback implements StatementCallback,
0419: SqlProvider {
0420: public Object doInStatement(Statement stmt)
0421: throws SQLException {
0422: ResultSet rs = null;
0423: try {
0424: rs = stmt.executeQuery(sql);
0425: ResultSet rsToUse = rs;
0426: if (nativeJdbcExtractor != null) {
0427: rsToUse = nativeJdbcExtractor
0428: .getNativeResultSet(rs);
0429: }
0430: return rse.extractData(rsToUse);
0431: } finally {
0432: JdbcUtils.closeResultSet(rs);
0433: }
0434: }
0435:
0436: public String getSql() {
0437: return sql;
0438: }
0439: }
0440: return execute(new QueryStatementCallback());
0441: }
0442:
0443: public void query(String sql, RowCallbackHandler rch)
0444: throws DataAccessException {
0445: query(sql, new RowCallbackHandlerResultSetExtractor(rch));
0446: }
0447:
0448: public List query(String sql, RowMapper rowMapper)
0449: throws DataAccessException {
0450: return (List) query(sql, new RowMapperResultSetExtractor(
0451: rowMapper));
0452: }
0453:
0454: public Map queryForMap(String sql) throws DataAccessException {
0455: return (Map) queryForObject(sql, getColumnMapRowMapper());
0456: }
0457:
0458: public Object queryForObject(String sql, RowMapper rowMapper)
0459: throws DataAccessException {
0460: List results = query(sql, rowMapper);
0461: return DataAccessUtils.requiredSingleResult(results);
0462: }
0463:
0464: public Object queryForObject(String sql, Class requiredType)
0465: throws DataAccessException {
0466: return queryForObject(sql,
0467: getSingleColumnRowMapper(requiredType));
0468: }
0469:
0470: public long queryForLong(String sql) throws DataAccessException {
0471: Number number = (Number) queryForObject(sql, Long.class);
0472: return (number != null ? number.longValue() : 0);
0473: }
0474:
0475: public int queryForInt(String sql) throws DataAccessException {
0476: Number number = (Number) queryForObject(sql, Integer.class);
0477: return (number != null ? number.intValue() : 0);
0478: }
0479:
0480: public List queryForList(String sql, Class elementType)
0481: throws DataAccessException {
0482: return query(sql, getSingleColumnRowMapper(elementType));
0483: }
0484:
0485: public List queryForList(String sql) throws DataAccessException {
0486: return query(sql, getColumnMapRowMapper());
0487: }
0488:
0489: public SqlRowSet queryForRowSet(String sql)
0490: throws DataAccessException {
0491: return (SqlRowSet) query(sql, new SqlRowSetResultSetExtractor());
0492: }
0493:
0494: public int update(final String sql) throws DataAccessException {
0495: Assert.notNull(sql, "SQL must not be null");
0496: if (logger.isDebugEnabled()) {
0497: logger.debug("Executing SQL update [" + sql + "]");
0498: }
0499:
0500: class UpdateStatementCallback implements StatementCallback,
0501: SqlProvider {
0502: public Object doInStatement(Statement stmt)
0503: throws SQLException {
0504: int rows = stmt.executeUpdate(sql);
0505: if (logger.isDebugEnabled()) {
0506: logger.debug("SQL update affected " + rows
0507: + " rows");
0508: }
0509: return new Integer(rows);
0510: }
0511:
0512: public String getSql() {
0513: return sql;
0514: }
0515: }
0516: return ((Integer) execute(new UpdateStatementCallback()))
0517: .intValue();
0518: }
0519:
0520: public int[] batchUpdate(final String[] sql)
0521: throws DataAccessException {
0522: Assert.notEmpty(sql, "SQL array must not be empty");
0523: if (logger.isDebugEnabled()) {
0524: logger.debug("Executing SQL batch update of " + sql.length
0525: + " statements");
0526: }
0527:
0528: class BatchUpdateStatementCallback implements
0529: StatementCallback, SqlProvider {
0530: private String currSql;
0531:
0532: public Object doInStatement(Statement stmt)
0533: throws SQLException, DataAccessException {
0534: int[] rowsAffected = new int[sql.length];
0535: if (JdbcUtils
0536: .supportsBatchUpdates(stmt.getConnection())) {
0537: for (int i = 0; i < sql.length; i++) {
0538: this .currSql = sql[i];
0539: stmt.addBatch(sql[i]);
0540: }
0541: rowsAffected = stmt.executeBatch();
0542: } else {
0543: for (int i = 0; i < sql.length; i++) {
0544: this .currSql = sql[i];
0545: if (!stmt.execute(sql[i])) {
0546: rowsAffected[i] = stmt.getUpdateCount();
0547: } else {
0548: throw new InvalidDataAccessApiUsageException(
0549: "Invalid batch SQL statement: "
0550: + sql[i]);
0551: }
0552: }
0553: }
0554: return rowsAffected;
0555: }
0556:
0557: public String getSql() {
0558: return currSql;
0559: }
0560: }
0561: return (int[]) execute(new BatchUpdateStatementCallback());
0562: }
0563:
0564: //-------------------------------------------------------------------------
0565: // Methods dealing with prepared statements
0566: //-------------------------------------------------------------------------
0567:
0568: public Object execute(PreparedStatementCreator psc,
0569: PreparedStatementCallback action)
0570: throws DataAccessException {
0571:
0572: Assert
0573: .notNull(psc,
0574: "PreparedStatementCreator must not be null");
0575: Assert.notNull(action, "Callback object must not be null");
0576: if (logger.isDebugEnabled()) {
0577: String sql = getSql(psc);
0578: logger.debug("Executing prepared SQL statement"
0579: + (sql != null ? " [" + sql + "]" : ""));
0580: }
0581:
0582: Connection con = DataSourceUtils.getConnection(getDataSource());
0583: PreparedStatement ps = null;
0584: try {
0585: Connection conToUse = con;
0586: if (this .nativeJdbcExtractor != null
0587: && this .nativeJdbcExtractor
0588: .isNativeConnectionNecessaryForNativePreparedStatements()) {
0589: conToUse = this .nativeJdbcExtractor
0590: .getNativeConnection(con);
0591: }
0592: ps = psc.createPreparedStatement(conToUse);
0593: applyStatementSettings(ps);
0594: PreparedStatement psToUse = ps;
0595: if (this .nativeJdbcExtractor != null) {
0596: psToUse = this .nativeJdbcExtractor
0597: .getNativePreparedStatement(ps);
0598: }
0599: Object result = action.doInPreparedStatement(psToUse);
0600: handleWarnings(ps.getWarnings());
0601: return result;
0602: } catch (SQLException ex) {
0603: // Release Connection early, to avoid potential connection pool deadlock
0604: // in the case when the exception translator hasn't been initialized yet.
0605: if (psc instanceof ParameterDisposer) {
0606: ((ParameterDisposer) psc).cleanupParameters();
0607: }
0608: String sql = getSql(psc);
0609: psc = null;
0610: JdbcUtils.closeStatement(ps);
0611: ps = null;
0612: DataSourceUtils.releaseConnection(con, getDataSource());
0613: con = null;
0614: throw getExceptionTranslator().translate(
0615: "PreparedStatementCallback", sql, ex);
0616: } finally {
0617: if (psc instanceof ParameterDisposer) {
0618: ((ParameterDisposer) psc).cleanupParameters();
0619: }
0620: JdbcUtils.closeStatement(ps);
0621: DataSourceUtils.releaseConnection(con, getDataSource());
0622: }
0623: }
0624:
0625: public Object execute(String sql, PreparedStatementCallback action)
0626: throws DataAccessException {
0627: return execute(new SimplePreparedStatementCreator(sql), action);
0628: }
0629:
0630: /**
0631: * Query using a prepared statement, allowing for a PreparedStatementCreator
0632: * and a PreparedStatementSetter. Most other query methods use this method,
0633: * but application code will always work with either a creator or a setter.
0634: * @param psc Callback handler that can create a PreparedStatement given a
0635: * Connection
0636: * @param pss object that knows how to set values on the prepared statement.
0637: * If this is null, the SQL will be assumed to contain no bind parameters.
0638: * @param rse object that will extract results.
0639: * @return an arbitrary result object, as returned by the ResultSetExtractor
0640: * @throws DataAccessException if there is any problem
0641: */
0642: public Object query(PreparedStatementCreator psc,
0643: final PreparedStatementSetter pss,
0644: final ResultSetExtractor rse) throws DataAccessException {
0645:
0646: Assert.notNull(rse, "ResultSetExtractor must not be null");
0647: logger.debug("Executing prepared SQL query");
0648:
0649: return execute(psc, new PreparedStatementCallback() {
0650: public Object doInPreparedStatement(PreparedStatement ps)
0651: throws SQLException {
0652: ResultSet rs = null;
0653: try {
0654: if (pss != null) {
0655: pss.setValues(ps);
0656: }
0657: rs = ps.executeQuery();
0658: ResultSet rsToUse = rs;
0659: if (nativeJdbcExtractor != null) {
0660: rsToUse = nativeJdbcExtractor
0661: .getNativeResultSet(rs);
0662: }
0663: return rse.extractData(rsToUse);
0664: } finally {
0665: JdbcUtils.closeResultSet(rs);
0666: if (pss instanceof ParameterDisposer) {
0667: ((ParameterDisposer) pss).cleanupParameters();
0668: }
0669: }
0670: }
0671: });
0672: }
0673:
0674: public Object query(PreparedStatementCreator psc,
0675: ResultSetExtractor rse) throws DataAccessException {
0676: return query(psc, null, rse);
0677: }
0678:
0679: public Object query(String sql, PreparedStatementSetter pss,
0680: ResultSetExtractor rse) throws DataAccessException {
0681: return query(new SimplePreparedStatementCreator(sql), pss, rse);
0682: }
0683:
0684: public Object query(String sql, Object[] args, int[] argTypes,
0685: ResultSetExtractor rse) throws DataAccessException {
0686: return query(sql, new ArgTypePreparedStatementSetter(args,
0687: argTypes), rse);
0688: }
0689:
0690: public Object query(String sql, Object[] args,
0691: ResultSetExtractor rse) throws DataAccessException {
0692: return query(sql, new ArgPreparedStatementSetter(args), rse);
0693: }
0694:
0695: public void query(PreparedStatementCreator psc,
0696: RowCallbackHandler rch) throws DataAccessException {
0697: query(psc, new RowCallbackHandlerResultSetExtractor(rch));
0698: }
0699:
0700: public void query(String sql, PreparedStatementSetter pss,
0701: RowCallbackHandler rch) throws DataAccessException {
0702: query(sql, pss, new RowCallbackHandlerResultSetExtractor(rch));
0703: }
0704:
0705: public void query(String sql, Object[] args, int[] argTypes,
0706: RowCallbackHandler rch) throws DataAccessException {
0707: query(sql, new ArgTypePreparedStatementSetter(args, argTypes),
0708: rch);
0709: }
0710:
0711: public void query(String sql, Object[] args, RowCallbackHandler rch)
0712: throws DataAccessException {
0713: query(sql, new ArgPreparedStatementSetter(args), rch);
0714: }
0715:
0716: public List query(PreparedStatementCreator psc, RowMapper rowMapper)
0717: throws DataAccessException {
0718: return (List) query(psc, new RowMapperResultSetExtractor(
0719: rowMapper));
0720: }
0721:
0722: public List query(String sql, PreparedStatementSetter pss,
0723: RowMapper rowMapper) throws DataAccessException {
0724: return (List) query(sql, pss, new RowMapperResultSetExtractor(
0725: rowMapper));
0726: }
0727:
0728: public List query(String sql, Object[] args, int[] argTypes,
0729: RowMapper rowMapper) throws DataAccessException {
0730: return (List) query(sql, args, argTypes,
0731: new RowMapperResultSetExtractor(rowMapper));
0732: }
0733:
0734: public List query(String sql, Object[] args, RowMapper rowMapper)
0735: throws DataAccessException {
0736: return (List) query(sql, args, new RowMapperResultSetExtractor(
0737: rowMapper));
0738: }
0739:
0740: public Object queryForObject(String sql, Object[] args,
0741: int[] argTypes, RowMapper rowMapper)
0742: throws DataAccessException {
0743:
0744: List results = (List) query(sql, args, argTypes,
0745: new RowMapperResultSetExtractor(rowMapper, 1));
0746: return DataAccessUtils.requiredSingleResult(results);
0747: }
0748:
0749: public Object queryForObject(String sql, Object[] args,
0750: RowMapper rowMapper) throws DataAccessException {
0751: List results = (List) query(sql, args,
0752: new RowMapperResultSetExtractor(rowMapper, 1));
0753: return DataAccessUtils.requiredSingleResult(results);
0754: }
0755:
0756: public Object queryForObject(String sql, Object[] args,
0757: int[] argTypes, Class requiredType)
0758: throws DataAccessException {
0759:
0760: return queryForObject(sql, args, argTypes,
0761: getSingleColumnRowMapper(requiredType));
0762: }
0763:
0764: public Object queryForObject(String sql, Object[] args,
0765: Class requiredType) throws DataAccessException {
0766: return queryForObject(sql, args,
0767: getSingleColumnRowMapper(requiredType));
0768: }
0769:
0770: public Map queryForMap(String sql, Object[] args, int[] argTypes)
0771: throws DataAccessException {
0772: return (Map) queryForObject(sql, args, argTypes,
0773: getColumnMapRowMapper());
0774: }
0775:
0776: public Map queryForMap(String sql, Object[] args)
0777: throws DataAccessException {
0778: return (Map) queryForObject(sql, args, getColumnMapRowMapper());
0779: }
0780:
0781: public long queryForLong(String sql, Object[] args, int[] argTypes)
0782: throws DataAccessException {
0783: Number number = (Number) queryForObject(sql, args, argTypes,
0784: Long.class);
0785: return (number != null ? number.longValue() : 0);
0786: }
0787:
0788: public long queryForLong(String sql, Object[] args)
0789: throws DataAccessException {
0790: Number number = (Number) queryForObject(sql, args, Long.class);
0791: return (number != null ? number.longValue() : 0);
0792: }
0793:
0794: public int queryForInt(String sql, Object[] args, int[] argTypes)
0795: throws DataAccessException {
0796: Number number = (Number) queryForObject(sql, args, argTypes,
0797: Integer.class);
0798: return (number != null ? number.intValue() : 0);
0799: }
0800:
0801: public int queryForInt(String sql, Object[] args)
0802: throws DataAccessException {
0803: Number number = (Number) queryForObject(sql, args,
0804: Integer.class);
0805: return (number != null ? number.intValue() : 0);
0806: }
0807:
0808: public List queryForList(String sql, Object[] args, int[] argTypes,
0809: Class elementType) throws DataAccessException {
0810: return query(sql, args, argTypes,
0811: getSingleColumnRowMapper(elementType));
0812: }
0813:
0814: public List queryForList(String sql, Object[] args,
0815: Class elementType) throws DataAccessException {
0816: return query(sql, args, getSingleColumnRowMapper(elementType));
0817: }
0818:
0819: public List queryForList(String sql, Object[] args, int[] argTypes)
0820: throws DataAccessException {
0821: return query(sql, args, argTypes, getColumnMapRowMapper());
0822: }
0823:
0824: public List queryForList(String sql, Object[] args)
0825: throws DataAccessException {
0826: return query(sql, args, getColumnMapRowMapper());
0827: }
0828:
0829: public SqlRowSet queryForRowSet(String sql, Object[] args,
0830: int[] argTypes) throws DataAccessException {
0831: return (SqlRowSet) query(sql, args, argTypes,
0832: new SqlRowSetResultSetExtractor());
0833: }
0834:
0835: public SqlRowSet queryForRowSet(String sql, Object[] args)
0836: throws DataAccessException {
0837: return (SqlRowSet) query(sql, args,
0838: new SqlRowSetResultSetExtractor());
0839: }
0840:
0841: protected int update(final PreparedStatementCreator psc,
0842: final PreparedStatementSetter pss)
0843: throws DataAccessException {
0844:
0845: logger.debug("Executing prepared SQL update");
0846:
0847: Integer result = (Integer) execute(psc,
0848: new PreparedStatementCallback() {
0849: public Object doInPreparedStatement(
0850: PreparedStatement ps) throws SQLException {
0851: try {
0852: if (pss != null) {
0853: pss.setValues(ps);
0854: }
0855: int rows = ps.executeUpdate();
0856: if (logger.isDebugEnabled()) {
0857: logger.debug("SQL update affected "
0858: + rows + " rows");
0859: }
0860: return new Integer(rows);
0861: } finally {
0862: if (pss instanceof ParameterDisposer) {
0863: ((ParameterDisposer) pss)
0864: .cleanupParameters();
0865: }
0866: }
0867: }
0868: });
0869: return result.intValue();
0870: }
0871:
0872: public int update(PreparedStatementCreator psc)
0873: throws DataAccessException {
0874: return update(psc, (PreparedStatementSetter) null);
0875: }
0876:
0877: public int update(final PreparedStatementCreator psc,
0878: final KeyHolder generatedKeyHolder)
0879: throws DataAccessException {
0880:
0881: Assert
0882: .notNull(generatedKeyHolder,
0883: "KeyHolder must not be null");
0884: logger
0885: .debug("Executing SQL update and returning generated keys");
0886:
0887: Integer result = (Integer) execute(psc,
0888: new PreparedStatementCallback() {
0889: public Object doInPreparedStatement(
0890: PreparedStatement ps) throws SQLException {
0891: int rows = ps.executeUpdate();
0892: List generatedKeys = generatedKeyHolder
0893: .getKeyList();
0894: generatedKeys.clear();
0895: ResultSet keys = ps.getGeneratedKeys();
0896: if (keys != null) {
0897: try {
0898: RowMapper rowMapper = getColumnMapRowMapper();
0899: RowMapperResultSetExtractor rse = new RowMapperResultSetExtractor(
0900: rowMapper, 1);
0901: generatedKeys.addAll((List) rse
0902: .extractData(keys));
0903: } finally {
0904: JdbcUtils.closeResultSet(keys);
0905: }
0906: }
0907: if (logger.isDebugEnabled()) {
0908: logger.debug("SQL update affected " + rows
0909: + " rows and returned "
0910: + generatedKeys.size() + " keys");
0911: }
0912: return new Integer(rows);
0913: }
0914: });
0915: return result.intValue();
0916: }
0917:
0918: public int update(String sql, PreparedStatementSetter pss)
0919: throws DataAccessException {
0920: return update(new SimplePreparedStatementCreator(sql), pss);
0921: }
0922:
0923: public int update(String sql, Object[] args, int[] argTypes)
0924: throws DataAccessException {
0925: return update(sql, new ArgTypePreparedStatementSetter(args,
0926: argTypes));
0927: }
0928:
0929: public int update(String sql, Object[] args)
0930: throws DataAccessException {
0931: return update(sql, new ArgPreparedStatementSetter(args));
0932: }
0933:
0934: public int[] batchUpdate(String sql,
0935: final BatchPreparedStatementSetter pss)
0936: throws DataAccessException {
0937: if (logger.isDebugEnabled()) {
0938: logger.debug("Executing SQL batch update [" + sql + "]");
0939: }
0940:
0941: return (int[]) execute(sql, new PreparedStatementCallback() {
0942: public Object doInPreparedStatement(PreparedStatement ps)
0943: throws SQLException {
0944: try {
0945: int batchSize = pss.getBatchSize();
0946: InterruptibleBatchPreparedStatementSetter ipss = (pss instanceof InterruptibleBatchPreparedStatementSetter ? (InterruptibleBatchPreparedStatementSetter) pss
0947: : null);
0948: if (JdbcUtils.supportsBatchUpdates(ps
0949: .getConnection())) {
0950: for (int i = 0; i < batchSize; i++) {
0951: pss.setValues(ps, i);
0952: if (ipss != null
0953: && ipss.isBatchExhausted(i)) {
0954: break;
0955: }
0956: ps.addBatch();
0957: }
0958: return ps.executeBatch();
0959: } else {
0960: List rowsAffected = new ArrayList();
0961: for (int i = 0; i < batchSize; i++) {
0962: pss.setValues(ps, i);
0963: if (ipss != null
0964: && ipss.isBatchExhausted(i)) {
0965: break;
0966: }
0967: rowsAffected.add(new Integer(ps
0968: .executeUpdate()));
0969: }
0970: int[] rowsAffectedArray = new int[rowsAffected
0971: .size()];
0972: for (int i = 0; i < rowsAffectedArray.length; i++) {
0973: rowsAffectedArray[i] = ((Integer) rowsAffected
0974: .get(i)).intValue();
0975: }
0976: return rowsAffectedArray;
0977: }
0978: } finally {
0979: if (pss instanceof ParameterDisposer) {
0980: ((ParameterDisposer) pss).cleanupParameters();
0981: }
0982: }
0983: }
0984: });
0985: }
0986:
0987: //-------------------------------------------------------------------------
0988: // Methods dealing with callable statements
0989: //-------------------------------------------------------------------------
0990:
0991: public Object execute(CallableStatementCreator csc,
0992: CallableStatementCallback action)
0993: throws DataAccessException {
0994:
0995: Assert
0996: .notNull(csc,
0997: "CallableStatementCreator must not be null");
0998: Assert.notNull(action, "Callback object must not be null");
0999: if (logger.isDebugEnabled()) {
1000: String sql = getSql(csc);
1001: logger.debug("Calling stored procedure"
1002: + (sql != null ? " [" + sql + "]" : ""));
1003: }
1004:
1005: Connection con = DataSourceUtils.getConnection(getDataSource());
1006: CallableStatement cs = null;
1007: try {
1008: Connection conToUse = con;
1009: if (this .nativeJdbcExtractor != null) {
1010: conToUse = this .nativeJdbcExtractor
1011: .getNativeConnection(con);
1012: }
1013: cs = csc.createCallableStatement(conToUse);
1014: applyStatementSettings(cs);
1015: CallableStatement csToUse = cs;
1016: if (this .nativeJdbcExtractor != null) {
1017: csToUse = this .nativeJdbcExtractor
1018: .getNativeCallableStatement(cs);
1019: }
1020: Object result = action.doInCallableStatement(csToUse);
1021: handleWarnings(cs.getWarnings());
1022: return result;
1023: } catch (SQLException ex) {
1024: // Release Connection early, to avoid potential connection pool deadlock
1025: // in the case when the exception translator hasn't been initialized yet.
1026: if (csc instanceof ParameterDisposer) {
1027: ((ParameterDisposer) csc).cleanupParameters();
1028: }
1029: String sql = getSql(csc);
1030: csc = null;
1031: JdbcUtils.closeStatement(cs);
1032: cs = null;
1033: DataSourceUtils.releaseConnection(con, getDataSource());
1034: con = null;
1035: throw getExceptionTranslator().translate(
1036: "CallableStatementCallback", sql, ex);
1037: } finally {
1038: if (csc instanceof ParameterDisposer) {
1039: ((ParameterDisposer) csc).cleanupParameters();
1040: }
1041: JdbcUtils.closeStatement(cs);
1042: DataSourceUtils.releaseConnection(con, getDataSource());
1043: }
1044: }
1045:
1046: public Object execute(String callString,
1047: CallableStatementCallback action)
1048: throws DataAccessException {
1049: return execute(new SimpleCallableStatementCreator(callString),
1050: action);
1051: }
1052:
1053: public Map call(CallableStatementCreator csc,
1054: List declaredParameters) throws DataAccessException {
1055: final List updateCountParameters = new ArrayList();
1056: final List resultSetParameters = new ArrayList();
1057: final List callParameters = new ArrayList();
1058: for (int i = 0; i < declaredParameters.size(); i++) {
1059: SqlParameter parameter = (SqlParameter) declaredParameters
1060: .get(i);
1061: if (parameter.isResultsParameter()) {
1062: if (parameter instanceof SqlReturnResultSet) {
1063: resultSetParameters.add(parameter);
1064: } else {
1065: updateCountParameters.add(parameter);
1066: }
1067: } else {
1068: callParameters.add(parameter);
1069: }
1070: }
1071: return (Map) execute(csc, new CallableStatementCallback() {
1072: public Object doInCallableStatement(CallableStatement cs)
1073: throws SQLException {
1074: boolean retVal = cs.execute();
1075: int updateCount = cs.getUpdateCount();
1076: if (logger.isDebugEnabled()) {
1077: logger
1078: .debug("CallableStatement.execute() returned '"
1079: + retVal + "'");
1080: logger
1081: .debug("CallableStatement.getUpdateCount() returned "
1082: + updateCount);
1083: }
1084: Map returnedResults = createResultsMap();
1085: if (retVal || updateCount != -1) {
1086: returnedResults.putAll(extractReturnedResults(cs,
1087: updateCountParameters, resultSetParameters,
1088: updateCount));
1089: }
1090: returnedResults.putAll(extractOutputParameters(cs,
1091: callParameters));
1092: return returnedResults;
1093: }
1094: });
1095: }
1096:
1097: /**
1098: * Extract returned ResultSets from the completed stored procedure.
1099: * @param cs JDBC wrapper for the stored procedure
1100: * @param updateCountParameters Parameter list of declared update count parameters for the stored procedure
1101: * @param resultSetParameters Parameter list of declared resturn resultSet parameters for the stored procedure
1102: * @return Map that contains returned results
1103: */
1104: protected Map extractReturnedResults(CallableStatement cs,
1105: List updateCountParameters, List resultSetParameters,
1106: int updateCount) throws SQLException {
1107:
1108: Map returnedResults = new HashMap();
1109: int rsIndex = 0;
1110: int updateIndex = 0;
1111: boolean moreResults;
1112: if (!skipResultsProcessing) {
1113: do {
1114: if (updateCount == -1) {
1115: SqlReturnResultSet rsParam = null;
1116: if (resultSetParameters != null
1117: && resultSetParameters.size() > rsIndex) {
1118: rsParam = (SqlReturnResultSet) resultSetParameters
1119: .get(rsIndex);
1120: } else {
1121: String rsName = RETURN_RESULT_SET_PREFIX
1122: + (rsIndex + 1);
1123: rsParam = new SqlReturnResultSet(rsName,
1124: new ColumnMapRowMapper());
1125: logger
1126: .info("Added default SqlReturnResultSet parameter named "
1127: + rsName);
1128: }
1129: returnedResults.putAll(processResultSet(cs
1130: .getResultSet(), rsParam));
1131: rsIndex++;
1132: } else {
1133: String ucName = null;
1134: if (updateCountParameters != null
1135: && updateCountParameters.size() > rsIndex) {
1136: SqlReturnUpdateCount ucParam = (SqlReturnUpdateCount) updateCountParameters
1137: .get(rsIndex);
1138: ucName = ucParam.getName();
1139: } else {
1140: ucName = RETURN_UPDATE_COUNT_PREFIX
1141: + (updateIndex + 1);
1142: logger
1143: .info("Added default SqlReturnUpdateCount parameter named "
1144: + ucName);
1145: }
1146: returnedResults.put(ucName, Integer
1147: .valueOf(updateCount));
1148: updateIndex++;
1149: }
1150: moreResults = cs.getMoreResults();
1151: updateCount = cs.getUpdateCount();
1152: if (logger.isDebugEnabled()) {
1153: logger
1154: .debug("CallableStatement.getUpdateCount() returned "
1155: + updateCount);
1156: }
1157: } while (moreResults || updateCount != -1);
1158: }
1159: return returnedResults;
1160: }
1161:
1162: /**
1163: * Extract output parameters from the completed stored procedure.
1164: * @param cs JDBC wrapper for the stored procedure
1165: * @param parameters parameter list for the stored procedure
1166: * @return parameters to the stored procedure
1167: * @return Map that contains returned results
1168: */
1169: protected Map extractOutputParameters(CallableStatement cs,
1170: List parameters) throws SQLException {
1171: Map returnedResults = new HashMap();
1172: int sqlColIndex = 1;
1173: for (int i = 0; i < parameters.size(); i++) {
1174: SqlParameter param = (SqlParameter) parameters.get(i);
1175: if (param instanceof SqlOutParameter) {
1176: SqlOutParameter outParam = (SqlOutParameter) param;
1177: if (outParam.isReturnTypeSupported()) {
1178: Object out = outParam.getSqlReturnType()
1179: .getTypeValue(cs, sqlColIndex,
1180: outParam.getSqlType(),
1181: outParam.getTypeName());
1182: returnedResults.put(outParam.getName(), out);
1183: } else {
1184: Object out = cs.getObject(sqlColIndex);
1185: if (out instanceof ResultSet) {
1186: if (outParam.isResultSetSupported()) {
1187: returnedResults.putAll(processResultSet(
1188: (ResultSet) out, outParam));
1189: } else {
1190: String rsName = outParam.getName();
1191: SqlReturnResultSet rsParam = new SqlReturnResultSet(
1192: rsName, new ColumnMapRowMapper());
1193: returnedResults.putAll(processResultSet(cs
1194: .getResultSet(), rsParam));
1195: logger
1196: .info("Added default SqlReturnResultSet parameter named "
1197: + rsName);
1198: }
1199: } else {
1200: returnedResults.put(outParam.getName(), out);
1201: }
1202: }
1203: }
1204: if (!(param.isResultsParameter())) {
1205: sqlColIndex++;
1206: }
1207: }
1208: return returnedResults;
1209: }
1210:
1211: /**
1212: * Process the given ResultSet from a stored procedure.
1213: * @param rs the ResultSet to process
1214: * @param param the corresponding stored procedure parameter
1215: * @return Map that contains returned results
1216: */
1217: protected Map processResultSet(ResultSet rs,
1218: ResultSetSupportingSqlParameter param) throws SQLException {
1219: Map returnedResults = new HashMap();
1220: try {
1221: ResultSet rsToUse = rs;
1222: if (this .nativeJdbcExtractor != null) {
1223: rsToUse = this .nativeJdbcExtractor
1224: .getNativeResultSet(rs);
1225: }
1226: if (param.getRowMapper() != null) {
1227: RowMapper rowMapper = param.getRowMapper();
1228: Object result = (new RowMapperResultSetExtractor(
1229: rowMapper)).extractData(rsToUse);
1230: returnedResults.put(param.getName(), result);
1231: } else if (param.getRowCallbackHandler() != null) {
1232: RowCallbackHandler rch = param.getRowCallbackHandler();
1233: (new RowCallbackHandlerResultSetExtractor(rch))
1234: .extractData(rsToUse);
1235: returnedResults
1236: .put(param.getName(),
1237: "ResultSet returned from stored procedure was processed");
1238: } else if (param.getResultSetExtractor() != null) {
1239: Object result = param.getResultSetExtractor()
1240: .extractData(rsToUse);
1241: returnedResults.put(param.getName(), result);
1242: }
1243: } finally {
1244: JdbcUtils.closeResultSet(rs);
1245: }
1246: return returnedResults;
1247: }
1248:
1249: //-------------------------------------------------------------------------
1250: // Implementation hooks and helper methods
1251: //-------------------------------------------------------------------------
1252:
1253: /**
1254: * Create a new RowMapper for reading columns as key-value pairs.
1255: * @return the RowMapper to use
1256: * @see ColumnMapRowMapper
1257: */
1258: protected RowMapper getColumnMapRowMapper() {
1259: return new ColumnMapRowMapper();
1260: }
1261:
1262: /**
1263: * Create a new RowMapper for reading result objects from a single column.
1264: * @param requiredType the type that each result object is expected to match
1265: * @return the RowMapper to use
1266: * @see SingleColumnRowMapper
1267: */
1268: protected RowMapper getSingleColumnRowMapper(Class requiredType) {
1269: return new SingleColumnRowMapper(requiredType);
1270: }
1271:
1272: /**
1273: * Prepare the given JDBC Statement (or PreparedStatement or CallableStatement),
1274: * applying statement settings such as fetch size, max rows, and query timeout.
1275: * @param stmt the JDBC Statement to prepare
1276: * @throws SQLException if thrown by JDBC API
1277: * @see #setFetchSize
1278: * @see #setMaxRows
1279: * @see #setQueryTimeout
1280: * @see org.springframework.jdbc.datasource.DataSourceUtils#applyTransactionTimeout
1281: */
1282: protected void applyStatementSettings(Statement stmt)
1283: throws SQLException {
1284: int fetchSize = getFetchSize();
1285: if (fetchSize > 0) {
1286: stmt.setFetchSize(fetchSize);
1287: }
1288: int maxRows = getMaxRows();
1289: if (maxRows > 0) {
1290: stmt.setMaxRows(maxRows);
1291: }
1292: DataSourceUtils.applyTimeout(stmt, getDataSource(),
1293: getQueryTimeout());
1294: }
1295:
1296: /**
1297: * Throw an SQLWarningException if we're not ignoring warnings,
1298: * else log the warnings (at debug level).
1299: * @param warning the warnings object from the current statement.
1300: * May be <code>null</code>, in which case this method does nothing.
1301: * @throws SQLWarningException if not ignoring warnings
1302: * @see org.springframework.jdbc.SQLWarningException
1303: */
1304: protected void handleWarnings(SQLWarning warning)
1305: throws SQLWarningException {
1306: if (warning != null) {
1307: if (isIgnoreWarnings()) {
1308: if (logger.isDebugEnabled()) {
1309: SQLWarning warningToLog = warning;
1310: while (warningToLog != null) {
1311: logger.debug("SQLWarning ignored: SQL state '"
1312: + warningToLog.getSQLState()
1313: + "', error code '"
1314: + warningToLog.getErrorCode()
1315: + "', message ["
1316: + warningToLog.getMessage() + "]");
1317: warningToLog = warningToLog.getNextWarning();
1318: }
1319: }
1320: } else {
1321: throw new SQLWarningException("Warning not ignored",
1322: warning);
1323: }
1324: }
1325: }
1326:
1327: /**
1328: * Determine SQL from potential provider object.
1329: * @param sqlProvider object that's potentially a SqlProvider
1330: * @return the SQL string, or <code>null</code>
1331: * @see SqlProvider
1332: */
1333: private static String getSql(Object sqlProvider) {
1334: if (sqlProvider instanceof SqlProvider) {
1335: return ((SqlProvider) sqlProvider).getSql();
1336: } else {
1337: return null;
1338: }
1339: }
1340:
1341: /**
1342: * Invocation handler that suppresses close calls on JDBC COnnections.
1343: * Also prepares returned Statement (Prepared/CallbackStatement) objects.
1344: * @see java.sql.Connection#close()
1345: */
1346: private class CloseSuppressingInvocationHandler implements
1347: InvocationHandler {
1348:
1349: private final Connection target;
1350:
1351: public CloseSuppressingInvocationHandler(Connection target) {
1352: this .target = target;
1353: }
1354:
1355: public Object invoke(Object proxy, Method method, Object[] args)
1356: throws Throwable {
1357: // Invocation on ConnectionProxy interface coming in...
1358:
1359: if (method.getName().equals("getTargetConnection")) {
1360: // Handle getTargetConnection method: return underlying Connection.
1361: return this .target;
1362: } else if (method.getName().equals("equals")) {
1363: // Only consider equal when proxies are identical.
1364: return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
1365: } else if (method.getName().equals("hashCode")) {
1366: // Use hashCode of PersistenceManager proxy.
1367: return new Integer(hashCode());
1368: } else if (method.getName().equals("close")) {
1369: // Handle close method: suppress, not valid.
1370: return null;
1371: }
1372:
1373: // Invoke method on target Connection.
1374: try {
1375: Object retVal = method.invoke(this .target, args);
1376:
1377: // If return value is a JDBC Statement, apply statement settings
1378: // (fetch size, max rows, transaction timeout).
1379: if (retVal instanceof Statement) {
1380: applyStatementSettings(((Statement) retVal));
1381: }
1382:
1383: return retVal;
1384: } catch (InvocationTargetException ex) {
1385: throw ex.getTargetException();
1386: }
1387: }
1388: }
1389:
1390: /**
1391: * Simple adapter for PreparedStatementCreator, allowing to use a plain SQL statement.
1392: */
1393: private static class SimplePreparedStatementCreator implements
1394: PreparedStatementCreator, SqlProvider {
1395:
1396: private final String sql;
1397:
1398: public SimplePreparedStatementCreator(String sql) {
1399: Assert.notNull(sql, "SQL must not be null");
1400: this .sql = sql;
1401: }
1402:
1403: public PreparedStatement createPreparedStatement(Connection con)
1404: throws SQLException {
1405: return con.prepareStatement(this .sql);
1406: }
1407:
1408: public String getSql() {
1409: return this .sql;
1410: }
1411: }
1412:
1413: /**
1414: * Simple adapter for CallableStatementCreator, allowing to use a plain SQL statement.
1415: */
1416: private static class SimpleCallableStatementCreator implements
1417: CallableStatementCreator, SqlProvider {
1418:
1419: private final String callString;
1420:
1421: public SimpleCallableStatementCreator(String callString) {
1422: Assert.notNull(callString, "Call string must not be null");
1423: this .callString = callString;
1424: }
1425:
1426: public CallableStatement createCallableStatement(Connection con)
1427: throws SQLException {
1428: return con.prepareCall(this .callString);
1429: }
1430:
1431: public String getSql() {
1432: return this .callString;
1433: }
1434: }
1435:
1436: /**
1437: * Adapter to enable use of a RowCallbackHandler inside a ResultSetExtractor.
1438: * <p>Uses a regular ResultSet, so we have to be careful when using it:
1439: * We don't use it for navigating since this could lead to unpredictable consequences.
1440: */
1441: private static class RowCallbackHandlerResultSetExtractor implements
1442: ResultSetExtractor {
1443:
1444: private final RowCallbackHandler rch;
1445:
1446: public RowCallbackHandlerResultSetExtractor(
1447: RowCallbackHandler rch) {
1448: this .rch = rch;
1449: }
1450:
1451: public Object extractData(ResultSet rs) throws SQLException {
1452: while (rs.next()) {
1453: this .rch.processRow(rs);
1454: }
1455: return null;
1456: }
1457: }
1458:
1459: /**
1460: * Create a Map instance to be used as results map.
1461: * <p>If "isResultsMapCaseInsensitive" has been set to true, a linked case-insensitive Map
1462: * will be created if possible, else a plain HashMap (see Spring's CollectionFactory).
1463: * @return the new Map instance
1464: * @see org.springframework.core.CollectionFactory#createLinkedCaseInsensitiveMapIfPossible
1465: */
1466: protected Map createResultsMap() {
1467: if (isResultsMapCaseInsensitive())
1468: return CollectionFactory
1469: .createLinkedCaseInsensitiveMapIfPossible(10);
1470: else
1471: return new HashMap();
1472: }
1473: }
|