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