1. /*
  2. * Copyright 2002-2004 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.springframework.jdbc.core;
  17. import java.sql.CallableStatement;
  18. import java.sql.Connection;
  19. import java.sql.DatabaseMetaData;
  20. import java.sql.PreparedStatement;
  21. import java.sql.ResultSet;
  22. import java.sql.ResultSetMetaData;
  23. import java.sql.SQLException;
  24. import java.sql.SQLWarning;
  25. import java.sql.Statement;
  26. import java.util.ArrayList;
  27. import java.util.HashMap;
  28. import java.util.LinkedHashMap;
  29. import java.util.List;
  30. import java.util.Map;
  31. import javax.sql.DataSource;
  32. import org.apache.commons.logging.Log;
  33. import org.apache.commons.logging.LogFactory;
  34. import org.springframework.beans.factory.InitializingBean;
  35. import org.springframework.core.JdkVersion;
  36. import org.springframework.dao.DataAccessException;
  37. import org.springframework.dao.IncorrectResultSizeDataAccessException;
  38. import org.springframework.dao.InvalidDataAccessApiUsageException;
  39. import org.springframework.dao.TypeMismatchDataAccessException;
  40. import org.springframework.jdbc.SQLWarningException;
  41. import org.springframework.jdbc.datasource.DataSourceUtils;
  42. import org.springframework.jdbc.support.JdbcAccessor;
  43. import org.springframework.jdbc.support.JdbcUtils;
  44. import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor;
  45. /**
  46. * <b>This is the central class in the JDBC core package.</b>
  47. * It simplifies the use of JDBC and helps to avoid common errors.
  48. * It executes core JDBC workflow, leaving application code to provide SQL
  49. * and extract results. This class executes SQL queries or updates, initiating
  50. * iteration over ResultSets and catching JDBC exceptions and translating
  51. * them to the generic, more informative exception hierarchy defined in the
  52. * org.springframework.dao package.
  53. *
  54. * <p>Code using this class need only implement callback interfaces, giving
  55. * them a clearly defined contract. The PreparedStatementCreator callback
  56. * interface creates a prepared statement given a Connection provided by this
  57. * class, providing SQL and any necessary parameters. The RowCallbackHandler
  58. * interface extracts values from each row of a ResultSet.
  59. *
  60. * <p>Can be used within a service implementation via direct instantiation
  61. * with a DataSource reference, or get prepared in an application context
  62. * and given to services as bean reference. Note: The DataSource should
  63. * always be configured as a bean in the application context, in the first case
  64. * given to the service directly, in the second case to the prepared template.
  65. *
  66. * <p>The motivation and design of this class is discussed
  67. * in detail in
  68. * <a href="http://www.amazon.com/exec/obidos/tg/detail/-/0764543857/">Expert One-On-One J2EE Design and Development</a>
  69. * by Rod Johnson (Wrox, 2002).
  70. *
  71. * <p>Because this class is parameterizable by the callback interfaces and
  72. * the SQLExceptionTranslator interface, it isn't necessary to subclass it.
  73. * All SQL issued by this class is logged.
  74. *
  75. * @author Rod Johnson
  76. * @author Juergen Hoeller
  77. * @author Yann Caroff
  78. * @author Thomas Risberg
  79. * @author Isabelle Muszynski
  80. * @version $Id: JdbcTemplate.java,v 1.45 2004/06/02 17:09:47 jhoeller Exp $
  81. * @since May 3, 2001
  82. * @see ResultSetExtractor
  83. * @see RowCallbackHandler
  84. * @see RowMapper
  85. * @see org.springframework.dao
  86. * @see org.springframework.jdbc.datasource
  87. * @see org.springframework.jdbc.object
  88. */
  89. public class JdbcTemplate extends JdbcAccessor implements JdbcOperations, InitializingBean {
  90. protected final Log logger = LogFactory.getLog(getClass());
  91. /** Custom NativeJdbcExtractor */
  92. private NativeJdbcExtractor nativeJdbcExtractor;
  93. /** If this variable is false, we will throw exceptions on SQL warnings */
  94. private boolean ignoreWarnings = true;
  95. /**
  96. * Construct a new JdbcTemplate for bean usage.
  97. * Note: The DataSource has to be set before using the instance.
  98. * This constructor can be used to prepare a JdbcTemplate via a BeanFactory,
  99. * typically setting the DataSource via setDataSource.
  100. * @see #setDataSource
  101. */
  102. public JdbcTemplate() {
  103. }
  104. /**
  105. * Construct a new JdbcTemplate, given a DataSource to obtain connections from.
  106. * Note: This will trigger eager initialization of the exception translator.
  107. * @param dataSource JDBC DataSource to obtain connections from
  108. */
  109. public JdbcTemplate(DataSource dataSource) {
  110. setDataSource(dataSource);
  111. afterPropertiesSet();
  112. }
  113. /**
  114. * Set a NativeJdbcExtractor to extract native JDBC objects from wrapped handles.
  115. * Useful if native Statement and/or ResultSet handles are expected for casting
  116. * to database-specific implementation classes, but a connection pool that wraps
  117. * JDBC objects is used (note: <i>any</i> pool will return wrapped Connections).
  118. */
  119. public void setNativeJdbcExtractor(NativeJdbcExtractor extractor) {
  120. this.nativeJdbcExtractor = extractor;
  121. }
  122. /**
  123. * Return the current NativeJdbcExtractor implementation.
  124. */
  125. public NativeJdbcExtractor getNativeJdbcExtractor() {
  126. return this.nativeJdbcExtractor;
  127. }
  128. /**
  129. * Set whether or not we want to ignore SQLWarnings.
  130. * Default is true.
  131. */
  132. public void setIgnoreWarnings(boolean ignoreWarnings) {
  133. this.ignoreWarnings = ignoreWarnings;
  134. }
  135. /**
  136. * Return whether or not we ignore SQLWarnings.
  137. * Default is true.
  138. */
  139. public boolean getIgnoreWarnings() {
  140. return ignoreWarnings;
  141. }
  142. //-------------------------------------------------------------------------
  143. // Methods dealing with static SQL (java.sql.Statement)
  144. //-------------------------------------------------------------------------
  145. public Object execute(final StatementCallback action) {
  146. Connection con = DataSourceUtils.getConnection(getDataSource());
  147. Statement stmt = null;
  148. try {
  149. Connection conToUse = con;
  150. if (this.nativeJdbcExtractor != null &&
  151. this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
  152. conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
  153. }
  154. stmt = conToUse.createStatement();
  155. DataSourceUtils.applyTransactionTimeout(stmt, getDataSource());
  156. Statement stmtToUse = stmt;
  157. if (this.nativeJdbcExtractor != null) {
  158. stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
  159. }
  160. Object result = action.doInStatement(stmtToUse);
  161. SQLWarning warning = stmt.getWarnings();
  162. throwExceptionOnWarningIfNotIgnoringWarnings(warning);
  163. return result;
  164. }
  165. catch (SQLException ex) {
  166. throw getExceptionTranslator().translate("executing StatementCallback", null, ex);
  167. }
  168. finally {
  169. JdbcUtils.closeStatement(stmt);
  170. DataSourceUtils.closeConnectionIfNecessary(con, getDataSource());
  171. }
  172. }
  173. public void execute(final String sql) throws DataAccessException {
  174. if (logger.isDebugEnabled()) {
  175. logger.debug("Executing SQL statement [" + sql + "]");
  176. }
  177. execute(new StatementCallback() {
  178. public Object doInStatement(Statement stmt) throws SQLException {
  179. stmt.execute(sql);
  180. return null;
  181. }
  182. });
  183. }
  184. public Object query(final String sql, final ResultSetExtractor rse) throws DataAccessException {
  185. if (sql == null) {
  186. throw new InvalidDataAccessApiUsageException("SQL must not be null");
  187. }
  188. if (sql.indexOf("?") != -1) {
  189. throw new InvalidDataAccessApiUsageException(
  190. "Cannot execute [" + sql + "] as a static query: it contains bind variables");
  191. }
  192. if (logger.isDebugEnabled()) {
  193. logger.debug("Executing SQL query [" + sql + "]");
  194. }
  195. return execute(new StatementCallback() {
  196. public Object doInStatement(Statement stmt) throws SQLException {
  197. ResultSet rs = null;
  198. try {
  199. rs = stmt.executeQuery(sql);
  200. ResultSet rsToUse = rs;
  201. if (nativeJdbcExtractor != null) {
  202. rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
  203. }
  204. return rse.extractData(rsToUse);
  205. }
  206. finally {
  207. JdbcUtils.closeResultSet(rs);
  208. }
  209. }
  210. });
  211. }
  212. public List query(String sql, RowCallbackHandler rch) throws DataAccessException {
  213. return (List) query(sql, new RowCallbackHandlerResultSetExtractor(rch));
  214. }
  215. public List queryForList(String sql) throws DataAccessException {
  216. return (List) query(sql, new ListResultSetExtractor());
  217. }
  218. public Object queryForObject(String sql, Class requiredType) throws DataAccessException {
  219. return query(sql, new ObjectResultSetExtractor(requiredType));
  220. }
  221. public long queryForLong(String sql) throws DataAccessException {
  222. Number number = (Number) queryForObject(sql, Number.class);
  223. return (number != null ? number.longValue() : 0);
  224. }
  225. public int queryForInt(String sql) throws DataAccessException {
  226. Number number = (Number) queryForObject(sql, Number.class);
  227. return (number != null ? number.intValue() : 0);
  228. }
  229. public int update(final String sql) throws DataAccessException {
  230. if (logger.isDebugEnabled()) {
  231. logger.debug("Executing SQL update [" + sql + "]");
  232. }
  233. Integer result = (Integer) execute(new StatementCallback() {
  234. public Object doInStatement(Statement stmt) throws SQLException {
  235. int rows = stmt.executeUpdate(sql);
  236. if (logger.isDebugEnabled()) {
  237. logger.debug("SQL update affected " + rows + " rows");
  238. }
  239. return new Integer(rows);
  240. }
  241. });
  242. return result.intValue();
  243. }
  244. //-------------------------------------------------------------------------
  245. // Methods dealing with prepared statements
  246. //-------------------------------------------------------------------------
  247. public Object execute(PreparedStatementCreator psc, PreparedStatementCallback action) {
  248. Connection con = DataSourceUtils.getConnection(getDataSource());
  249. PreparedStatement ps = null;
  250. try {
  251. Connection conToUse = con;
  252. if (this.nativeJdbcExtractor != null &&
  253. this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) {
  254. conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
  255. }
  256. ps = psc.createPreparedStatement(conToUse);
  257. DataSourceUtils.applyTransactionTimeout(ps, getDataSource());
  258. PreparedStatement psToUse = ps;
  259. if (this.nativeJdbcExtractor != null) {
  260. psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps);
  261. }
  262. Object result = action.doInPreparedStatement(psToUse);
  263. SQLWarning warning = ps.getWarnings();
  264. throwExceptionOnWarningIfNotIgnoringWarnings(warning);
  265. return result;
  266. }
  267. catch (SQLException ex) {
  268. throw getExceptionTranslator().translate("executing PreparedStatementCallback [" + psc + "]",
  269. getSql(psc), ex);
  270. }
  271. finally {
  272. JdbcUtils.closeStatement(ps);
  273. DataSourceUtils.closeConnectionIfNecessary(con, getDataSource());
  274. }
  275. }
  276. public Object execute(final String sql, PreparedStatementCallback action) {
  277. return execute(new SimplePreparedStatementCreator(sql), action);
  278. }
  279. /**
  280. * Query using a prepared statement, allowing for a PreparedStatementCreator
  281. * and a PreparedStatementSetter. Most other query methods use this method,
  282. * but application code will always work with either a creator or a setter.
  283. * @param psc Callback handler that can create a PreparedStatement given a
  284. * Connection
  285. * @param pss object that knows how to set values on the prepared statement.
  286. * If this is null, the SQL will be assumed to contain no bind parameters.
  287. * @param rse object that will extract results.
  288. * @return an arbitrary result object, as returned by the ResultSetExtractor
  289. * @throws DataAccessException if there is any problem
  290. */
  291. protected Object query(PreparedStatementCreator psc, final PreparedStatementSetter pss,
  292. final ResultSetExtractor rse) throws DataAccessException {
  293. if (logger.isDebugEnabled()) {
  294. String sql = getSql(psc);
  295. logger.debug("Executing SQL query" + (sql != null ? " [" + sql + "]" : ""));
  296. }
  297. return execute(psc, new PreparedStatementCallback() {
  298. public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
  299. if (pss != null) {
  300. pss.setValues(ps);
  301. }
  302. ResultSet rs = null;
  303. try {
  304. rs = ps.executeQuery();
  305. ResultSet rsToUse = rs;
  306. if (nativeJdbcExtractor != null) {
  307. rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
  308. }
  309. return rse.extractData(rsToUse);
  310. }
  311. finally {
  312. JdbcUtils.closeResultSet(rs);
  313. }
  314. }
  315. });
  316. }
  317. public Object query(PreparedStatementCreator psc, ResultSetExtractor rse) {
  318. return query(psc, null, rse);
  319. }
  320. public Object query(final String sql, final PreparedStatementSetter pss, final ResultSetExtractor rse)
  321. throws DataAccessException {
  322. if (sql == null) {
  323. throw new InvalidDataAccessApiUsageException("SQL may not be null");
  324. }
  325. return query(new SimplePreparedStatementCreator(sql), pss, rse);
  326. }
  327. public Object query(String sql, Object[] args, int[] argTypes, ResultSetExtractor rse) {
  328. return query(sql, new ArgTypePreparedStatementSetter(args, argTypes), rse);
  329. }
  330. public Object query(String sql, Object[] args, ResultSetExtractor rse) {
  331. return query(sql, new ArgPreparedStatementSetter(args), rse);
  332. }
  333. public List query(PreparedStatementCreator psc, RowCallbackHandler rch)
  334. throws DataAccessException {
  335. return (List) query(psc, new RowCallbackHandlerResultSetExtractor(rch));
  336. }
  337. public List query(String sql, final PreparedStatementSetter pss, final RowCallbackHandler rch)
  338. throws DataAccessException {
  339. return (List) query(sql, pss, new RowCallbackHandlerResultSetExtractor(rch));
  340. }
  341. public List query(String sql, final Object[] args, final int[] argTypes, RowCallbackHandler rch)
  342. throws DataAccessException {
  343. return query(sql, new ArgTypePreparedStatementSetter(args, argTypes), rch);
  344. }
  345. public List query(String sql, final Object[] args, RowCallbackHandler rch)
  346. throws DataAccessException {
  347. return query(sql, new ArgPreparedStatementSetter(args), rch);
  348. }
  349. public List queryForList(String sql, final Object[] args) throws DataAccessException {
  350. return (List) query(sql,
  351. new ArgPreparedStatementSetter(args),
  352. new ListResultSetExtractor());
  353. }
  354. public Object queryForObject(String sql, Object[] args, Class requiredType)
  355. throws DataAccessException {
  356. return query(sql,
  357. new ArgPreparedStatementSetter(args),
  358. new ObjectResultSetExtractor(requiredType));
  359. }
  360. public long queryForLong(String sql, final Object[] args) throws DataAccessException {
  361. Number number = (Number) queryForObject(sql, args, Number.class);
  362. return (number != null ? number.longValue() : 0);
  363. }
  364. public int queryForInt(String sql, final Object[] args) throws DataAccessException {
  365. Number number = (Number) queryForObject(sql, args, Number.class);
  366. return (number != null ? number.intValue() : 0);
  367. }
  368. protected int update(PreparedStatementCreator psc, final PreparedStatementSetter pss) throws DataAccessException {
  369. if (logger.isDebugEnabled()) {
  370. String sql = getSql(psc);
  371. logger.debug("Executing SQL update" + (sql != null ? " [" + sql + "]" : ""));
  372. }
  373. Integer result = (Integer) execute(psc, new PreparedStatementCallback() {
  374. public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
  375. if (pss != null) {
  376. pss.setValues(ps);
  377. }
  378. int rows = ps.executeUpdate();
  379. if (logger.isDebugEnabled()) {
  380. logger.debug("SQL update affected " + rows + " rows");
  381. }
  382. return new Integer(rows);
  383. }
  384. });
  385. return result.intValue();
  386. }
  387. public int update(PreparedStatementCreator psc) throws DataAccessException {
  388. return update(psc, null);
  389. }
  390. public int update(String sql, final PreparedStatementSetter pss) throws DataAccessException {
  391. return update(new SimplePreparedStatementCreator(sql), pss);
  392. }
  393. public int update(String sql, final Object[] args, final int[] argTypes) throws DataAccessException {
  394. return update(sql, new ArgTypePreparedStatementSetter(args, argTypes));
  395. }
  396. public int update(String sql, final Object[] args) throws DataAccessException {
  397. return update(sql, new ArgPreparedStatementSetter(args));
  398. }
  399. public int[] batchUpdate(String sql, final BatchPreparedStatementSetter pss) throws DataAccessException {
  400. if (logger.isDebugEnabled()) {
  401. logger.debug("Executing SQL batch update [" + sql + "]");
  402. }
  403. return (int[]) execute(sql, new PreparedStatementCallback() {
  404. public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
  405. int batchSize = pss.getBatchSize();
  406. DatabaseMetaData dbmd = ps.getConnection().getMetaData();
  407. if (dbmd != null && dbmd.supportsBatchUpdates()) {
  408. for (int i = 0; i < batchSize; i++) {
  409. pss.setValues(ps, i);
  410. ps.addBatch();
  411. }
  412. return ps.executeBatch();
  413. }
  414. else {
  415. int[] rowsAffected = new int[batchSize];
  416. for (int i = 0; i < batchSize; i++) {
  417. pss.setValues(ps, i);
  418. rowsAffected[i] = ps.executeUpdate();
  419. }
  420. return rowsAffected;
  421. }
  422. }
  423. });
  424. }
  425. //-------------------------------------------------------------------------
  426. // Methods dealing with callable statements
  427. //-------------------------------------------------------------------------
  428. public Object execute(CallableStatementCreator csc, CallableStatementCallback action) {
  429. if (logger.isDebugEnabled()) {
  430. String sql = getSql(csc);
  431. logger.debug("Calling stored procedure" + (sql != null ? " [" + sql + "]" : ""));
  432. }
  433. Connection con = DataSourceUtils.getConnection(getDataSource());
  434. CallableStatement cs = null;
  435. try {
  436. Connection conToUse = con;
  437. if (this.nativeJdbcExtractor != null &&
  438. this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeCallableStatements()) {
  439. conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
  440. }
  441. cs = csc.createCallableStatement(conToUse);
  442. DataSourceUtils.applyTransactionTimeout(cs, getDataSource());
  443. CallableStatement csToUse = cs;
  444. if (nativeJdbcExtractor != null) {
  445. csToUse = nativeJdbcExtractor.getNativeCallableStatement(cs);
  446. }
  447. Object result = action.doInCallableStatement(csToUse);
  448. SQLWarning warning = cs.getWarnings();
  449. throwExceptionOnWarningIfNotIgnoringWarnings(warning);
  450. return result;
  451. }
  452. catch (SQLException ex) {
  453. throw getExceptionTranslator().translate("executing CallableStatementCallback [" + csc + "]",
  454. getSql(csc), ex);
  455. }
  456. finally {
  457. JdbcUtils.closeStatement(cs);
  458. DataSourceUtils.closeConnectionIfNecessary(con, getDataSource());
  459. }
  460. }
  461. public Object execute(final String callString, CallableStatementCallback action) {
  462. return execute(new SimpleCallableStatementCreator(callString), action);
  463. }
  464. public Map call(CallableStatementCreator csc, final List declaredParameters) throws DataAccessException {
  465. return (Map) execute(csc, new CallableStatementCallback() {
  466. public Object doInCallableStatement(CallableStatement cs) throws SQLException {
  467. boolean retVal = cs.execute();
  468. if (logger.isDebugEnabled()) {
  469. logger.debug("CallableStatement.execute returned [" + retVal + "]");
  470. }
  471. Map returnedResults = new HashMap();
  472. if (retVal) {
  473. returnedResults.putAll(extractReturnedResultSets(cs, declaredParameters));
  474. }
  475. returnedResults.putAll(extractOutputParameters(cs, declaredParameters));
  476. return returnedResults;
  477. }
  478. });
  479. }
  480. /**
  481. * Extract returned ResultSets from the completed stored procedure.
  482. * @param cs JDBC wrapper for the stored procedure
  483. * @param parameters Parameter list for the stored procedure
  484. * @return Map that contains returned results
  485. */
  486. protected Map extractReturnedResultSets(CallableStatement cs, List parameters) throws SQLException {
  487. Map returnedResults = new HashMap();
  488. int rsIndex = 0;
  489. do {
  490. Object param = null;
  491. if (parameters != null && parameters.size() > rsIndex) {
  492. param = parameters.get(rsIndex);
  493. }
  494. if (param instanceof SqlReturnResultSet) {
  495. SqlReturnResultSet rsParam = (SqlReturnResultSet) param;
  496. returnedResults.putAll(processResultSet(cs.getResultSet(), rsParam));
  497. }
  498. else {
  499. logger.warn("ResultSet returned from stored procedure but a corresponding " +
  500. "SqlReturnResultSet parameter was not declared");
  501. }
  502. rsIndex++;
  503. }
  504. while (cs.getMoreResults());
  505. return returnedResults;
  506. }
  507. /**
  508. * Extract output parameters from the completed stored procedure.
  509. * @param cs JDBC wrapper for the stored procedure
  510. * @param parameters parameter list for the stored procedure
  511. * @return parameters to the stored procedure
  512. * @return Map that contains returned results
  513. */
  514. protected Map extractOutputParameters(CallableStatement cs, List parameters) throws SQLException {
  515. Map returnedResults = new HashMap();
  516. int sqlColIndex = 1;
  517. for (int i = 0; i < parameters.size(); i++) {
  518. Object param = parameters.get(i);
  519. if (param instanceof SqlOutParameter) {
  520. SqlOutParameter outParam = (SqlOutParameter) param;
  521. Object out = cs.getObject(sqlColIndex);
  522. if (out instanceof ResultSet) {
  523. if (outParam.isResultSetSupported()) {
  524. returnedResults.putAll(processResultSet((ResultSet) out, outParam));
  525. }
  526. else {
  527. logger.warn("ResultSet returned from stored procedure but a corresponding " +
  528. "SqlOutParameter with a RowCallbackHandler was not declared");
  529. returnedResults.put(outParam.getName(), "ResultSet was returned but not processed.");
  530. }
  531. }
  532. else {
  533. returnedResults.put(outParam.getName(), out);
  534. }
  535. }
  536. if (!(param instanceof SqlReturnResultSet)) {
  537. sqlColIndex++;
  538. }
  539. }
  540. return returnedResults;
  541. }
  542. /**
  543. * Process the given ResultSet from a stored procedure.
  544. * @param rs the ResultSet to process
  545. * @param param the corresponding stored procedure parameter
  546. * @return Map that contains returned results
  547. */
  548. protected Map processResultSet(ResultSet rs, ResultSetSupportingSqlParameter param) throws SQLException {
  549. Map returnedResults = new HashMap();
  550. try {
  551. ResultSet rsToUse = rs;
  552. if (this.nativeJdbcExtractor != null) {
  553. rsToUse = this.nativeJdbcExtractor.getNativeResultSet(rs);
  554. }
  555. if (param.isRowCallbackHandlerSupported()) {
  556. // It's a RowCallbackHandler or RowMapper.
  557. // We'll get a RowCallbackHandler to use in both cases.
  558. RowCallbackHandler rch = param.getRowCallbackHandler();
  559. (new RowCallbackHandlerResultSetExtractor(rch)).extractData(rsToUse);
  560. if (rch instanceof ResultReader) {
  561. returnedResults.put(param.getName(), ((ResultReader) rch).getResults());
  562. }
  563. else {
  564. returnedResults.put(param.getName(), "ResultSet returned from stored procedure was processed.");
  565. }
  566. }
  567. else {
  568. // It's a ResultSetExtractor - simply apply it.
  569. Object result = param.getResultSetExtractor().extractData(rsToUse);
  570. returnedResults.put(param.getName(), result);
  571. }
  572. }
  573. finally {
  574. JdbcUtils.closeResultSet(rs);
  575. }
  576. return returnedResults;
  577. }
  578. /**
  579. * Throw an SQLWarningException if we're not ignoring warnings.
  580. * @param warning warning from current statement. May be null,
  581. * in which case this method does nothing.
  582. */
  583. private void throwExceptionOnWarningIfNotIgnoringWarnings(SQLWarning warning) throws SQLWarningException {
  584. if (warning != null) {
  585. if (this.ignoreWarnings) {
  586. logger.warn("SQLWarning ignored: " + warning);
  587. }
  588. else {
  589. throw new SQLWarningException("Warning not ignored", warning);
  590. }
  591. }
  592. }
  593. /**
  594. * Determine SQL from potential provider object.
  595. * @param sqlProvider object that's potentially a SqlProvider
  596. * @return the SQL string, or null
  597. * @see SqlProvider
  598. */
  599. private String getSql(Object sqlProvider) {
  600. if (sqlProvider instanceof SqlProvider) {
  601. return ((SqlProvider) sqlProvider).getSql();
  602. }
  603. else {
  604. return null;
  605. }
  606. }
  607. /**
  608. * Simple adapter for PreparedStatementCreator, allowing to use a plain SQL statement.
  609. */
  610. private static class SimplePreparedStatementCreator
  611. implements PreparedStatementCreator, SqlProvider {
  612. private final String sql;
  613. public SimplePreparedStatementCreator(String sql) {
  614. this.sql = sql;
  615. }
  616. public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
  617. return con.prepareStatement(this.sql);
  618. }
  619. public String getSql() {
  620. return sql;
  621. }
  622. }
  623. /**
  624. * Simple adapter for CallableStatementCreator, allowing to use a plain SQL statement.
  625. */
  626. private static class SimpleCallableStatementCreator
  627. implements CallableStatementCreator, SqlProvider {
  628. private final String callString;
  629. public SimpleCallableStatementCreator(String callString) {
  630. this.callString = callString;
  631. }
  632. public CallableStatement createCallableStatement(Connection con) throws SQLException {
  633. return con.prepareCall(this.callString);
  634. }
  635. public String getSql() {
  636. return callString;
  637. }
  638. }
  639. /**
  640. * Simple adapter for PreparedStatementSetter that applies
  641. * a given array of arguments.
  642. */
  643. private static class ArgPreparedStatementSetter implements PreparedStatementSetter {
  644. private final Object[] args;
  645. public ArgPreparedStatementSetter(Object[] args) {
  646. this.args = args;
  647. }
  648. public void setValues(PreparedStatement ps) throws SQLException {
  649. if (this.args != null) {
  650. for (int i = 0; i < this.args.length; i++) {
  651. ps.setObject(i + 1, this.args[i]);
  652. }
  653. }
  654. }
  655. }
  656. /**
  657. * Simple adapter for PreparedStatementSetter that applies
  658. * given arrays of arguments and JDBC argument types.
  659. */
  660. private static class ArgTypePreparedStatementSetter implements PreparedStatementSetter {
  661. private final Object[] args;
  662. private final int[] argTypes;
  663. public ArgTypePreparedStatementSetter(Object[] args, int[] argTypes) {
  664. if ((args != null && argTypes == null) || (args == null && argTypes != null) ||
  665. (args != null && args.length != argTypes.length)) {
  666. throw new InvalidDataAccessApiUsageException("args and argTypes parameters must match");
  667. }
  668. this.args = args;
  669. this.argTypes = argTypes;
  670. }
  671. public void setValues(PreparedStatement ps) throws SQLException {
  672. if (this.args != null) {
  673. for (int i = 0; i < this.args.length; i++) {
  674. ps.setObject(i + 1, this.args[i], this.argTypes[i]);
  675. }
  676. }
  677. }
  678. }
  679. /**
  680. * Adapter to enable use of a RowCallbackHandler inside a ResultSetExtractor.
  681. * <p>Uses a regular ResultSet, so we have to be careful when using it:
  682. * We don't use it for navigating since this could lead to unpredictable consequences.
  683. */
  684. private static class RowCallbackHandlerResultSetExtractor implements ResultSetExtractor {
  685. private final RowCallbackHandler rch;
  686. public RowCallbackHandlerResultSetExtractor(RowCallbackHandler rch) {
  687. this.rch = rch;
  688. }
  689. public Object extractData(ResultSet rs) throws SQLException {
  690. while (rs.next()) {
  691. this.rch.processRow(rs);
  692. }
  693. if (this.rch instanceof ResultReader) {
  694. return ((ResultReader) this.rch).getResults();
  695. }
  696. else {
  697. return null;
  698. }
  699. }
  700. }
  701. /**
  702. * ResultSetExtractor implementation that returns an ArrayList of HashMaps.
  703. */
  704. private static class ListResultSetExtractor implements ResultSetExtractor {
  705. public Object extractData(ResultSet rs) throws SQLException {
  706. ResultSetMetaData rsmd = rs.getMetaData();
  707. int numberOfColumns = rsmd.getColumnCount();
  708. List listOfRows = new ArrayList();
  709. while (rs.next()) {
  710. Map mapOfColValues = null;
  711. // A LinkedHashMap will preserve column order, but is not available pre-1.4.
  712. if (JdkVersion.getMajorJavaVersion() >= JdkVersion.JAVA_14) {
  713. mapOfColValues = LinkedHashMapCreator.createLinkedHashMap(numberOfColumns);
  714. }
  715. else {
  716. mapOfColValues = new HashMap(numberOfColumns);
  717. }
  718. for (int i = 1; i <= numberOfColumns; i++) {
  719. mapOfColValues.put(rsmd.getColumnName(i), rs.getObject(i));
  720. }
  721. listOfRows.add(mapOfColValues);
  722. }
  723. return listOfRows;
  724. }
  725. }
  726. /**
  727. * ResultSetExtractor implementation that returns single result object.
  728. */
  729. private static class ObjectResultSetExtractor implements ResultSetExtractor {
  730. private final Class requiredType;
  731. public ObjectResultSetExtractor(Class requiredType) {
  732. this.requiredType = requiredType;
  733. }
  734. public Object extractData(ResultSet rs) throws SQLException {
  735. ResultSetMetaData rsmd = rs.getMetaData();
  736. int nrOfColumns = rsmd.getColumnCount();
  737. if (nrOfColumns != 1) {
  738. throw new IncorrectResultSizeDataAccessException("Expected single column but found " + nrOfColumns,
  739. 1, nrOfColumns);
  740. }
  741. if (!rs.next()) {
  742. throw new IncorrectResultSizeDataAccessException("Expected single row but found none", 1, 0);
  743. }
  744. Object result = rs.getObject(1);
  745. if (rs.next()) {
  746. throw new IncorrectResultSizeDataAccessException("Expected single row but found more than one", 1, -1);
  747. }
  748. if (result != null && this.requiredType != null && !this.requiredType.isInstance(result)) {
  749. throw new TypeMismatchDataAccessException("Result object (db-type=\"" + rsmd.getColumnTypeName(1) +
  750. "\" value=\"" + result + "\") is of type [" +
  751. rsmd.getColumnClassName(1) + "] and not of required type [" +
  752. this.requiredType.getName() + "]");
  753. }
  754. return result;
  755. }
  756. }
  757. /**
  758. * Actual creation of a java.util.LinkedHashMap.
  759. * In separate inner class to avoid runtime dependency on JDK 1.4.
  760. */
  761. private static abstract class LinkedHashMapCreator {
  762. private static Map createLinkedHashMap(int capacity) {
  763. return new LinkedHashMap(capacity);
  764. }
  765. }
  766. }