1 /*
2 * Copyright 2002-2008 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
17 package org.springframework.jdbc.core;
18
19 import java.lang.reflect.InvocationHandler;
20 import java.lang.reflect.InvocationTargetException;
21 import java.lang.reflect.Method;
22 import java.lang.reflect.Proxy;
23 import java.sql.CallableStatement;
24 import java.sql.Connection;
25 import java.sql.PreparedStatement;
26 import java.sql.ResultSet;
27 import java.sql.SQLException;
28 import java.sql.SQLWarning;
29 import java.sql.Statement;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.LinkedHashMap;
34 import java.util.List;
35 import java.util.Map;
36
37 import javax.sql.DataSource;
38
39 import org.springframework.core.CollectionFactory;
40 import org.springframework.dao.DataAccessException;
41 import org.springframework.dao.InvalidDataAccessApiUsageException;
42 import org.springframework.dao.support.DataAccessUtils;
43 import org.springframework.jdbc.SQLWarningException;
44 import org.springframework.jdbc.datasource.ConnectionProxy;
45 import org.springframework.jdbc.datasource.DataSourceUtils;
46 import org.springframework.jdbc.support.JdbcAccessor;
47 import org.springframework.jdbc.support.JdbcUtils;
48 import org.springframework.jdbc.support.KeyHolder;
49 import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor;
50 import org.springframework.jdbc.support.rowset.SqlRowSet;
51 import org.springframework.util.Assert;
52
53 /**
54 * <b>This is the central class in the JDBC core package.</b>
55 * It simplifies the use of JDBC and helps to avoid common errors.
56 * It executes core JDBC workflow, leaving application code to provide SQL
57 * and extract results. This class executes SQL queries or updates, initiating
58 * iteration over ResultSets and catching JDBC exceptions and translating
59 * them to the generic, more informative exception hierarchy defined in the
60 * <code>org.springframework.dao</code> package.
61 *
62 * <p>Code using this class need only implement callback interfaces, giving
63 * them a clearly defined contract. The {@link PreparedStatementCreator} callback
64 * interface creates a prepared statement given a Connection, providing SQL and
65 * any necessary parameters. The {@link ResultSetExtractor} interface extracts
66 * values from a ResultSet. See also {@link PreparedStatementSetter} and
67 * {@link RowMapper} for two popular alternative callback interfaces.
68 *
69 * <p>Can be used within a service implementation via direct instantiation
70 * with a DataSource reference, or get prepared in an application context
71 * and given to services as bean reference. Note: The DataSource should
72 * always be configured as a bean in the application context, in the first case
73 * given to the service directly, in the second case to the prepared template.
74 *
75 * <p>Because this class is parameterizable by the callback interfaces and
76 * the {@link org.springframework.jdbc.support.SQLExceptionTranslator}
77 * interface, there should be no need to subclass it.
78 *
79 * <p>All SQL operations performed by this class are logged at debug level,
80 * using "org.springframework.jdbc.core.JdbcTemplate" as log category.
81 *
82 * @author Rod Johnson
83 * @author Juergen Hoeller
84 * @author Thomas Risberg
85 * @since May 3, 2001
86 * @see PreparedStatementCreator
87 * @see PreparedStatementSetter
88 * @see CallableStatementCreator
89 * @see PreparedStatementCallback
90 * @see CallableStatementCallback
91 * @see ResultSetExtractor
92 * @see RowCallbackHandler
93 * @see RowMapper
94 * @see org.springframework.jdbc.support.SQLExceptionTranslator
95 */
96 public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
97
98 private static final String RETURN_RESULT_SET_PREFIX = "#result-set-";
99
100 private static final String RETURN_UPDATE_COUNT_PREFIX = "#update-count-";
101
102
103 /** Custom NativeJdbcExtractor */
104 private NativeJdbcExtractor nativeJdbcExtractor;
105
106 /** If this variable is false, we will throw exceptions on SQL warnings */
107 private boolean ignoreWarnings = true;
108
109 /**
110 * If this variable is set to a non-zero value, it will be used for setting the
111 * fetchSize property on statements used for query processing.
112 */
113 private int fetchSize = 0;
114
115 /**
116 * If this variable is set to a non-zero value, it will be used for setting the
117 * maxRows property on statements used for query processing.
118 */
119 private int maxRows = 0;
120
121 /**
122 * If this variable is set to a non-zero value, it will be used for setting the
123 * queryTimeout property on statements used for query processing.
124 */
125 private int queryTimeout = 0;
126
127 /**
128 * If this variable is set to true then all results checking will be bypassed for any
129 * callable statement processing. This can be used to avoid a bug in some older Oracle
130 * JDBC drivers like 10.1.0.2.
131 */
132 private boolean skipResultsProcessing = false;
133
134 /**
135 * If this variable is set to true then all results from a stored procedure call
136 * that don't have a corresponding SqlOutParameter declaration will be bypassed.
137 * All other results processng will be take place unless the variable
138 * <code>skipResultsProcessing</code> is set to <code>true</code>
139 */
140 private boolean skipUndeclaredResults = false;
141
142 /**
143 * If this variable is set to true then execution of a CallableStatement will return
144 * the results in a Map that uses case insensitive names for the parameters if
145 * Commons Collections is available on the classpath.
146 */
147 private boolean resultsMapCaseInsensitive = false;
148
149
150 /**
151 * Construct a new JdbcTemplate for bean usage.
152 * <p>Note: The DataSource has to be set before using the instance.
153 * @see #setDataSource
154 */
155 public JdbcTemplate() {
156 }
157
158 /**
159 * Construct a new JdbcTemplate, given a DataSource to obtain connections from.
160 * <p>Note: This will not trigger initialization of the exception translator.
161 * @param dataSource the JDBC DataSource to obtain connections from
162 */
163 public JdbcTemplate(DataSource dataSource) {
164 setDataSource(dataSource);
165 afterPropertiesSet();
166 }
167
168 /**
169 * Construct a new JdbcTemplate, given a DataSource to obtain connections from.
170 * <p>Note: Depending on the "lazyInit" flag, initialization of the exception translator
171 * will be triggered.
172 * @param dataSource the JDBC DataSource to obtain connections from
173 * @param lazyInit whether to lazily initialize the SQLExceptionTranslator
174 */
175 public JdbcTemplate(DataSource dataSource, boolean lazyInit) {
176 setDataSource(dataSource);
177 setLazyInit(lazyInit);
178 afterPropertiesSet();
179 }
180
181
182 /**
183 * Set a NativeJdbcExtractor to extract native JDBC objects from wrapped handles.
184 * Useful if native Statement and/or ResultSet handles are expected for casting
185 * to database-specific implementation classes, but a connection pool that wraps
186 * JDBC objects is used (note: <i>any</i> pool will return wrapped Connections).
187 */
188 public void setNativeJdbcExtractor(NativeJdbcExtractor extractor) {
189 this.nativeJdbcExtractor = extractor;
190 }
191
192 /**
193 * Return the current NativeJdbcExtractor implementation.
194 */
195 public NativeJdbcExtractor getNativeJdbcExtractor() {
196 return this.nativeJdbcExtractor;
197 }
198
199 /**
200 * Set whether or not we want to ignore SQLWarnings.
201 * <p>Default is "true", swallowing and logging all warnings. Switch this flag
202 * to "false" to make the JdbcTemplate throw a SQLWarningException instead.
203 * @see java.sql.SQLWarning
204 * @see org.springframework.jdbc.SQLWarningException
205 * @see #handleWarnings
206 */
207 public void setIgnoreWarnings(boolean ignoreWarnings) {
208 this.ignoreWarnings = ignoreWarnings;
209 }
210
211 /**
212 * Return whether or not we ignore SQLWarnings.
213 */
214 public boolean isIgnoreWarnings() {
215 return this.ignoreWarnings;
216 }
217
218 /**
219 * Set the fetch size for this JdbcTemplate. This is important for processing
220 * large result sets: Setting this higher than the default value will increase
221 * processing speed at the cost of memory consumption; setting this lower can
222 * avoid transferring row data that will never be read by the application.
223 * <p>Default is 0, indicating to use the JDBC driver's default.
224 * @see java.sql.Statement#setFetchSize
225 */
226 public void setFetchSize(int fetchSize) {
227 this.fetchSize = fetchSize;
228 }
229
230 /**
231 * Return the fetch size specified for this JdbcTemplate.
232 */
233 public int getFetchSize() {
234 return this.fetchSize;
235 }
236
237 /**
238 * Set the maximum number of rows for this JdbcTemplate. This is important
239 * for processing subsets of large result sets, avoiding to read and hold
240 * the entire result set in the database or in the JDBC driver if we're
241 * never interested in the entire result in the first place (for example,
242 * when performing searches that might return a large number of matches).
243 * <p>Default is 0, indicating to use the JDBC driver's default.
244 * @see java.sql.Statement#setMaxRows
245 */
246 public void setMaxRows(int maxRows) {
247 this.maxRows = maxRows;
248 }
249
250 /**
251 * Return the maximum number of rows specified for this JdbcTemplate.
252 */
253 public int getMaxRows() {
254 return this.maxRows;
255 }
256
257 /**
258 * Set the query timeout for statements that this JdbcTemplate executes.
259 * <p>Default is 0, indicating to use the JDBC driver's default.
260 * <p>Note: Any timeout specified here will be overridden by the remaining
261 * transaction timeout when executing within a transaction that has a
262 * timeout specified at the transaction level.
263 * @see java.sql.Statement#setQueryTimeout
264 */
265 public void setQueryTimeout(int queryTimeout) {
266 this.queryTimeout = queryTimeout;
267 }
268
269 /**
270 * Return the query timeout for statements that this JdbcTemplate executes.
271 */
272 public int getQueryTimeout() {
273 return this.queryTimeout;
274 }
275
276 /**
277 * Set whether results processing should be skipped. Can be used to optimize callable
278 * statement processing when we know that no results are being passed back - the processing
279 * of out parameter will still take place. This can be used to avoid a bug in some older
280 * Oracle JDBC drivers like 10.1.0.2.
281 */
282 public void setSkipResultsProcessing(boolean skipResultsProcessing) {
283 this.skipResultsProcessing = skipResultsProcessing;
284 }
285
286 /**
287 * Return whether results processing should be skipped.
288 */
289 public boolean isSkipResultsProcessing() {
290 return this.skipResultsProcessing;
291 }
292
293 /**
294 * Set whether undelared results should be skipped.
295 */
296 public void setSkipUndeclaredResults(boolean skipUndeclaredResults) {
297 this.skipUndeclaredResults = skipUndeclaredResults;
298 }
299
300 /**
301 * Return whether undeclared results should be skipped.
302 */
303 public boolean isSkipUndeclaredResults() {
304 return this.skipUndeclaredResults;
305 }
306
307 /**
308 * Set whether execution of a CallableStatement will return the results in a Map
309 * that uses case insensitive names for the parameters.
310 */
311 public void setResultsMapCaseInsensitive(boolean resultsMapCaseInsensitive) {
312 this.resultsMapCaseInsensitive = resultsMapCaseInsensitive;
313 }
314
315 /**
316 * Return whether execution of a CallableStatement will return the results in a Map
317 * that uses case insensitive names for the parameters.
318 */
319 public boolean isResultsMapCaseInsensitive() {
320 return this.resultsMapCaseInsensitive;
321 }
322
323
324 //-------------------------------------------------------------------------
325 // Methods dealing with a plain java.sql.Connection
326 //-------------------------------------------------------------------------
327
328 public Object execute(ConnectionCallback action) throws DataAccessException {
329 Assert.notNull(action, "Callback object must not be null");
330
331 Connection con = DataSourceUtils.getConnection(getDataSource());
332 try {
333 Connection conToUse = con;
334 if (this.nativeJdbcExtractor != null) {
335 // Extract native JDBC Connection, castable to OracleConnection or the like.
336 conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
337 }
338 else {
339 // Create close-suppressing Connection proxy, also preparing returned Statements.
340 conToUse = createConnectionProxy(con);
341 }
342 return action.doInConnection(conToUse);
343 }
344 catch (SQLException ex) {
345 // Release Connection early, to avoid potential connection pool deadlock
346 // in the case when the exception translator hasn't been initialized yet.
347 DataSourceUtils.releaseConnection(con, getDataSource());
348 con = null;
349 throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex);
350 }
351 finally {
352 DataSourceUtils.releaseConnection(con, getDataSource());
353 }
354 }
355
356 /**
357 * Create a close-suppressing proxy for the given JDBC Connection.
358 * Called by the <code>execute</code> method.
359 * <p>The proxy also prepares returned JDBC Statements, applying
360 * statement settings such as fetch size, max rows, and query timeout.
361 * @param con the JDBC Connection to create a proxy for
362 * @return the Connection proxy
363 * @see java.sql.Connection#close()
364 * @see #execute(ConnectionCallback)
365 * @see #applyStatementSettings
366 */
367 protected Connection createConnectionProxy(Connection con) {
368 return (Connection) Proxy.newProxyInstance(
369 ConnectionProxy.class.getClassLoader(),
370 new Class[] {ConnectionProxy.class},
371 new CloseSuppressingInvocationHandler(con));
372 }
373
374
375 //-------------------------------------------------------------------------
376 // Methods dealing with static SQL (java.sql.Statement)
377 //-------------------------------------------------------------------------
378
379 public Object execute(StatementCallback action) throws DataAccessException {
380 Assert.notNull(action, "Callback object must not be null");
381
382 Connection con = DataSourceUtils.getConnection(getDataSource());
383 Statement stmt = null;
384 try {
385 Connection conToUse = con;
386 if (this.nativeJdbcExtractor != null &&
387 this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
388 conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
389 }
390 stmt = conToUse.createStatement();
391 applyStatementSettings(stmt);
392 Statement stmtToUse = stmt;
393 if (this.nativeJdbcExtractor != null) {
394 stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
395 }
396 Object result = action.doInStatement(stmtToUse);
397 handleWarnings(stmt);
398 return result;
399 }
400 catch (SQLException ex) {
401 // Release Connection early, to avoid potential connection pool deadlock
402 // in the case when the exception translator hasn't been initialized yet.
403 JdbcUtils.closeStatement(stmt);
404 stmt = null;
405 DataSourceUtils.releaseConnection(con, getDataSource());
406 con = null;
407 throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
408 }
409 finally {
410 JdbcUtils.closeStatement(stmt);
411 DataSourceUtils.releaseConnection(con, getDataSource());
412 }
413 }
414
415 public void execute(final String sql) throws DataAccessException {
416 if (logger.isDebugEnabled()) {
417 logger.debug("Executing SQL statement [" + sql + "]");
418 }
419
420 class ExecuteStatementCallback implements StatementCallback, SqlProvider {
421 public Object doInStatement(Statement stmt) throws SQLException {
422 stmt.execute(sql);
423 return null;
424 }
425 public String getSql() {
426 return sql;
427 }
428 }
429 execute(new ExecuteStatementCallback());
430 }
431
432 public Object query(final String sql, final ResultSetExtractor rse) throws DataAccessException {
433 Assert.notNull(sql, "SQL must not be null");
434 Assert.notNull(rse, "ResultSetExtractor must not be null");
435 if (logger.isDebugEnabled()) {
436 logger.debug("Executing SQL query [" + sql + "]");
437 }
438
439 class QueryStatementCallback implements StatementCallback, SqlProvider {
440 public Object doInStatement(Statement stmt) throws SQLException {
441 ResultSet rs = null;
442 try {
443 rs = stmt.executeQuery(sql);
444 ResultSet rsToUse = rs;
445 if (nativeJdbcExtractor != null) {
446 rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
447 }
448 return rse.extractData(rsToUse);
449 }
450 finally {
451 JdbcUtils.closeResultSet(rs);
452 }
453 }
454 public String getSql() {
455 return sql;
456 }
457 }
458 return execute(new QueryStatementCallback());
459 }
460
461 public void query(String sql, RowCallbackHandler rch) throws DataAccessException {
462 query(sql, new RowCallbackHandlerResultSetExtractor(rch));
463 }
464
465 public List query(String sql, RowMapper rowMapper) throws DataAccessException {
466 return (List) query(sql, new RowMapperResultSetExtractor(rowMapper));
467 }
468
469 public Map queryForMap(String sql) throws DataAccessException {
470 return (Map) queryForObject(sql, getColumnMapRowMapper());
471 }
472
473 public Object queryForObject(String sql, RowMapper rowMapper) throws DataAccessException {
474 List results = query(sql, rowMapper);
475 return DataAccessUtils.requiredSingleResult(results);
476 }
477
478 public Object queryForObject(String sql, Class requiredType) throws DataAccessException {
479 return queryForObject(sql, getSingleColumnRowMapper(requiredType));
480 }
481
482 public long queryForLong(String sql) throws DataAccessException {
483 Number number = (Number) queryForObject(sql, Long.class);
484 return (number != null ? number.longValue() : 0);
485 }
486
487 public int queryForInt(String sql) throws DataAccessException {
488 Number number = (Number) queryForObject(sql, Integer.class);
489 return (number != null ? number.intValue() : 0);
490 }
491
492 public List queryForList(String sql, Class elementType) throws DataAccessException {
493 return query(sql, getSingleColumnRowMapper(elementType));
494 }
495
496 public List queryForList(String sql) throws DataAccessException {
497 return query(sql, getColumnMapRowMapper());
498 }
499
500 public SqlRowSet queryForRowSet(String sql) throws DataAccessException {
501 return (SqlRowSet) query(sql, new SqlRowSetResultSetExtractor());
502 }
503
504 public int update(final String sql) throws DataAccessException {
505 Assert.notNull(sql, "SQL must not be null");
506 if (logger.isDebugEnabled()) {
507 logger.debug("Executing SQL update [" + sql + "]");
508 }
509
510 class UpdateStatementCallback implements StatementCallback, SqlProvider {
511 public Object doInStatement(Statement stmt) throws SQLException {
512 int rows = stmt.executeUpdate(sql);
513 if (logger.isDebugEnabled()) {
514 logger.debug("SQL update affected " + rows + " rows");
515 }
516 return new Integer(rows);
517 }
518 public String getSql() {
519 return sql;
520 }
521 }
522 return ((Integer) execute(new UpdateStatementCallback())).intValue();
523 }
524
525 public int[] batchUpdate(final String[] sql) throws DataAccessException {
526 Assert.notEmpty(sql, "SQL array must not be empty");
527 if (logger.isDebugEnabled()) {
528 logger.debug("Executing SQL batch update of " + sql.length + " statements");
529 }
530
531 class BatchUpdateStatementCallback implements StatementCallback, SqlProvider {
532 private String currSql;
533 public Object doInStatement(Statement stmt) throws SQLException, DataAccessException {
534 int[] rowsAffected = new int[sql.length];
535 if (JdbcUtils.supportsBatchUpdates(stmt.getConnection())) {
536 for (int i = 0; i < sql.length; i++) {
537 this.currSql = sql[i];
538 stmt.addBatch(sql[i]);
539 }
540 rowsAffected = stmt.executeBatch();
541 }
542 else {
543 for (int i = 0; i < sql.length; i++) {
544 this.currSql = sql[i];
545 if (!stmt.execute(sql[i])) {
546 rowsAffected[i] = stmt.getUpdateCount();
547 }
548 else {
549 throw new InvalidDataAccessApiUsageException("Invalid batch SQL statement: " + sql[i]);
550 }
551 }
552 }
553 return rowsAffected;
554 }
555 public String getSql() {
556 return currSql;
557 }
558 }
559 return (int[]) execute(new BatchUpdateStatementCallback());
560 }
561
562
563 //-------------------------------------------------------------------------
564 // Methods dealing with prepared statements
565 //-------------------------------------------------------------------------
566
567 public Object execute(PreparedStatementCreator psc, PreparedStatementCallback action)
568 throws DataAccessException {
569
570 Assert.notNull(psc, "PreparedStatementCreator must not be null");
571 Assert.notNull(action, "Callback object must not be null");
572 if (logger.isDebugEnabled()) {
573 String sql = getSql(psc);
574 logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));
575 }
576
577 Connection con = DataSourceUtils.getConnection(getDataSource());
578 PreparedStatement ps = null;
579 try {
580 Connection conToUse = con;
581 if (this.nativeJdbcExtractor != null &&
582 this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) {
583 conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
584 }
585 ps = psc.createPreparedStatement(conToUse);
586 applyStatementSettings(ps);
587 PreparedStatement psToUse = ps;
588 if (this.nativeJdbcExtractor != null) {
589 psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps);
590 }
591 Object result = action.doInPreparedStatement(psToUse);
592 handleWarnings(ps);
593 return result;
594 }
595 catch (SQLException ex) {
596 // Release Connection early, to avoid potential connection pool deadlock
597 // in the case when the exception translator hasn't been initialized yet.
598 if (psc instanceof ParameterDisposer) {
599 ((ParameterDisposer) psc).cleanupParameters();
600 }
601 String sql = getSql(psc);
602 psc = null;
603 JdbcUtils.closeStatement(ps);
604 ps = null;
605 DataSourceUtils.releaseConnection(con, getDataSource());
606 con = null;
607 throw getExceptionTranslator().translate("PreparedStatementCallback", sql, ex);
608 }
609 finally {
610 if (psc instanceof ParameterDisposer) {
611 ((ParameterDisposer) psc).cleanupParameters();
612 }
613 JdbcUtils.closeStatement(ps);
614 DataSourceUtils.releaseConnection(con, getDataSource());
615 }
616 }
617
618 public Object execute(String sql, PreparedStatementCallback action) throws DataAccessException {
619 return execute(new SimplePreparedStatementCreator(sql), action);
620 }
621
622 /**
623 * Query using a prepared statement, allowing for a PreparedStatementCreator
624 * and a PreparedStatementSetter. Most other query methods use this method,
625 * but application code will always work with either a creator or a setter.
626 * @param psc Callback handler that can create a PreparedStatement given a
627 * Connection
628 * @param pss object that knows how to set values on the prepared statement.
629 * If this is null, the SQL will be assumed to contain no bind parameters.
630 * @param rse object that will extract results.
631 * @return an arbitrary result object, as returned by the ResultSetExtractor
632 * @throws DataAccessException if there is any problem
633 */
634 public Object query(
635 PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor rse)
636 throws DataAccessException {
637
638 Assert.notNull(rse, "ResultSetExtractor must not be null");
639 logger.debug("Executing prepared SQL query");
640
641 return execute(psc, new PreparedStatementCallback() {
642 public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
643 ResultSet rs = null;
644 try {
645 if (pss != null) {
646 pss.setValues(ps);
647 }
648 rs = ps.executeQuery();
649 ResultSet rsToUse = rs;
650 if (nativeJdbcExtractor != null) {
651 rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
652 }
653 return rse.extractData(rsToUse);
654 }
655 finally {
656 JdbcUtils.closeResultSet(rs);
657 if (pss instanceof ParameterDisposer) {
658 ((ParameterDisposer) pss).cleanupParameters();
659 }
660 }
661 }
662 });
663 }
664
665 public Object query(PreparedStatementCreator psc, ResultSetExtractor rse) throws DataAccessException {
666 return query(psc, null, rse);
667 }
668
669 public Object query(String sql, PreparedStatementSetter pss, ResultSetExtractor rse) throws DataAccessException {
670 return query(new SimplePreparedStatementCreator(sql), pss, rse);
671 }
672
673 public Object query(String sql, Object[] args, int[] argTypes, ResultSetExtractor rse) throws DataAccessException {
674 return query(sql, new ArgTypePreparedStatementSetter(args, argTypes), rse);
675 }
676
677 public Object query(String sql, Object[] args, ResultSetExtractor rse) throws DataAccessException {
678 return query(sql, new ArgPreparedStatementSetter(args), rse);
679 }
680
681 public void query(PreparedStatementCreator psc, RowCallbackHandler rch) throws DataAccessException {
682 query(psc, new RowCallbackHandlerResultSetExtractor(rch));
683 }
684
685 public void query(String sql, PreparedStatementSetter pss, RowCallbackHandler rch) throws DataAccessException {
686 query(sql, pss, new RowCallbackHandlerResultSetExtractor(rch));
687 }
688
689 public void query(String sql, Object[] args, int[] argTypes, RowCallbackHandler rch) throws DataAccessException {
690 query(sql, new ArgTypePreparedStatementSetter(args, argTypes), rch);
691 }
692
693 public void query(String sql, Object[] args, RowCallbackHandler rch) throws DataAccessException {
694 query(sql, new ArgPreparedStatementSetter(args), rch);
695 }
696
697 public List query(PreparedStatementCreator psc, RowMapper rowMapper) throws DataAccessException {
698 return (List) query(psc, new RowMapperResultSetExtractor(rowMapper));
699 }
700
701 public List query(String sql, PreparedStatementSetter pss, RowMapper rowMapper) throws DataAccessException {
702 return (List) query(sql, pss, new RowMapperResultSetExtractor(rowMapper));
703 }
704
705 public List query(String sql, Object[] args, int[] argTypes, RowMapper rowMapper) throws DataAccessException {
706 return (List) query(sql, args, argTypes, new RowMapperResultSetExtractor(rowMapper));
707 }
708
709 public List query(String sql, Object[] args, RowMapper rowMapper) throws DataAccessException {
710 return (List) query(sql, args, new RowMapperResultSetExtractor(rowMapper));
711 }
712
713 public Object queryForObject(String sql, Object[] args, int[] argTypes, RowMapper rowMapper)
714 throws DataAccessException {
715
716 List results = (List) query(sql, args, argTypes, new RowMapperResultSetExtractor(rowMapper, 1));
717 return DataAccessUtils.requiredSingleResult(results);
718 }
719
720 public Object queryForObject(String sql, Object[] args, RowMapper rowMapper) throws DataAccessException {
721 List results = (List) query(sql, args, new RowMapperResultSetExtractor(rowMapper, 1));
722 return DataAccessUtils.requiredSingleResult(results);
723 }
724
725 public Object queryForObject(String sql, Object[] args, int[] argTypes, Class requiredType)
726 throws DataAccessException {
727
728 return queryForObject(sql, args, argTypes, getSingleColumnRowMapper(requiredType));
729 }
730
731 public Object queryForObject(String sql, Object[] args, Class requiredType) throws DataAccessException {
732 return queryForObject(sql, args, getSingleColumnRowMapper(requiredType));
733 }
734
735 public Map queryForMap(String sql, Object[] args, int[] argTypes) throws DataAccessException {
736 return (Map) queryForObject(sql, args, argTypes, getColumnMapRowMapper());
737 }
738
739 public Map queryForMap(String sql, Object[] args) throws DataAccessException {
740 return (Map) queryForObject(sql, args, getColumnMapRowMapper());
741 }
742
743 public long queryForLong(String sql, Object[] args, int[] argTypes) throws DataAccessException {
744 Number number = (Number) queryForObject(sql, args, argTypes, Long.class);
745 return (number != null ? number.longValue() : 0);
746 }
747
748 public long queryForLong(String sql, Object[] args) throws DataAccessException {
749 Number number = (Number) queryForObject(sql, args, Long.class);
750 return (number != null ? number.longValue() : 0);
751 }
752
753 public int queryForInt(String sql, Object[] args, int[] argTypes) throws DataAccessException {
754 Number number = (Number) queryForObject(sql, args, argTypes, Integer.class);
755 return (number != null ? number.intValue() : 0);
756 }
757
758 public int queryForInt(String sql, Object[] args) throws DataAccessException {
759 Number number = (Number) queryForObject(sql, args, Integer.class);
760 return (number != null ? number.intValue() : 0);
761 }
762
763 public List queryForList(String sql, Object[] args, int[] argTypes, Class elementType) throws DataAccessException {
764 return query(sql, args, argTypes, getSingleColumnRowMapper(elementType));
765 }
766
767 public List queryForList(String sql, Object[] args, Class elementType) throws DataAccessException {
768 return query(sql, args, getSingleColumnRowMapper(elementType));
769 }
770
771 public List queryForList(String sql, Object[] args, int[] argTypes) throws DataAccessException {
772 return query(sql, args, argTypes, getColumnMapRowMapper());
773 }
774
775 public List queryForList(String sql, Object[] args) throws DataAccessException {
776 return query(sql, args, getColumnMapRowMapper());
777 }
778
779 public SqlRowSet queryForRowSet(String sql, Object[] args, int[] argTypes) throws DataAccessException {
780 return (SqlRowSet) query(sql, args, argTypes, new SqlRowSetResultSetExtractor());
781 }
782
783 public SqlRowSet queryForRowSet(String sql, Object[] args) throws DataAccessException {
784 return (SqlRowSet) query(sql, args, new SqlRowSetResultSetExtractor());
785 }
786
787 protected int update(final PreparedStatementCreator psc, final PreparedStatementSetter pss)
788 throws DataAccessException {
789
790 logger.debug("Executing prepared SQL update");
791
792 Integer result = (Integer) execute(psc, new PreparedStatementCallback() {
793 public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
794 try {
795 if (pss != null) {
796 pss.setValues(ps);
797 }
798 int rows = ps.executeUpdate();
799 if (logger.isDebugEnabled()) {
800 logger.debug("SQL update affected " + rows + " rows");
801 }
802 return new Integer(rows);
803 }
804 finally {
805 if (pss instanceof ParameterDisposer) {
806 ((ParameterDisposer) pss).cleanupParameters();
807 }
808 }
809 }
810 });
811 return result.intValue();
812 }
813
814 public int update(PreparedStatementCreator psc) throws DataAccessException {
815 return update(psc, (PreparedStatementSetter) null);
816 }
817
818 public int update(final PreparedStatementCreator psc, final KeyHolder generatedKeyHolder)
819 throws DataAccessException {
820
821 Assert.notNull(generatedKeyHolder, "KeyHolder must not be null");
822 logger.debug("Executing SQL update and returning generated keys");
823
824 Integer result = (Integer) execute(psc, new PreparedStatementCallback() {
825 public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
826 int rows = ps.executeUpdate();
827 List generatedKeys = generatedKeyHolder.getKeyList();
828 generatedKeys.clear();
829 ResultSet keys = ps.getGeneratedKeys();
830 if (keys != null) {
831 try {
832 RowMapper rowMapper = getColumnMapRowMapper();
833 RowMapperResultSetExtractor rse = new RowMapperResultSetExtractor(rowMapper, 1);
834 generatedKeys.addAll((List) rse.extractData(keys));
835 }
836 finally {
837 JdbcUtils.closeResultSet(keys);
838 }
839 }
840 if (logger.isDebugEnabled()) {
841 logger.debug("SQL update affected " + rows + " rows and returned " + generatedKeys.size() + " keys");
842 }
843 return new Integer(rows);
844 }
845 });
846 return result.intValue();
847 }
848
849 public int update(String sql, PreparedStatementSetter pss) throws DataAccessException {
850 return update(new SimplePreparedStatementCreator(sql), pss);
851 }
852
853 public int update(String sql, Object[] args, int[] argTypes) throws DataAccessException {
854 return update(sql, new ArgTypePreparedStatementSetter(args, argTypes));
855 }
856
857 public int update(String sql, Object[] args) throws DataAccessException {
858 return update(sql, new ArgPreparedStatementSetter(args));
859 }
860
861 public int[] batchUpdate(String sql, final BatchPreparedStatementSetter pss) throws DataAccessException {
862 if (logger.isDebugEnabled()) {
863 logger.debug("Executing SQL batch update [" + sql + "]");
864 }
865
866 return (int[]) execute(sql, new PreparedStatementCallback() {
867 public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
868 try {
869 int batchSize = pss.getBatchSize();
870 InterruptibleBatchPreparedStatementSetter ipss =
871 (pss instanceof InterruptibleBatchPreparedStatementSetter ?
872 (InterruptibleBatchPreparedStatementSetter) pss : null);
873 if (JdbcUtils.supportsBatchUpdates(ps.getConnection())) {
874 for (int i = 0; i < batchSize; i++) {
875 pss.setValues(ps, i);
876 if (ipss != null && ipss.isBatchExhausted(i)) {
877 break;
878 }
879 ps.addBatch();
880 }
881 return ps.executeBatch();
882 }
883 else {
884 List rowsAffected = new ArrayList();
885 for (int i = 0; i < batchSize; i++) {
886 pss.setValues(ps, i);
887 if (ipss != null && ipss.isBatchExhausted(i)) {
888 break;
889 }
890 rowsAffected.add(new Integer(ps.executeUpdate()));
891 }
892 int[] rowsAffectedArray = new int[rowsAffected.size()];
893 for (int i = 0; i < rowsAffectedArray.length; i++) {
894 rowsAffectedArray[i] = ((Integer) rowsAffected.get(i)).intValue();
895 }
896 return rowsAffectedArray;
897 }
898 }
899 finally {
900 if (pss instanceof ParameterDisposer) {
901 ((ParameterDisposer) pss).cleanupParameters();
902 }
903 }
904 }
905 });
906 }
907
908
909 //-------------------------------------------------------------------------
910 // Methods dealing with callable statements
911 //-------------------------------------------------------------------------
912
913 public Object execute(CallableStatementCreator csc, CallableStatementCallback action)
914 throws DataAccessException {
915
916 Assert.notNull(csc, "CallableStatementCreator must not be null");
917 Assert.notNull(action, "Callback object must not be null");
918 if (logger.isDebugEnabled()) {
919 String sql = getSql(csc);
920 logger.debug("Calling stored procedure" + (sql != null ? " [" + sql + "]" : ""));
921 }
922
923 Connection con = DataSourceUtils.getConnection(getDataSource());
924 CallableStatement cs = null;
925 try {
926 Connection conToUse = con;
927 if (this.nativeJdbcExtractor != null) {
928 conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
929 }
930 cs = csc.createCallableStatement(conToUse);
931 applyStatementSettings(cs);
932 CallableStatement csToUse = cs;
933 if (this.nativeJdbcExtractor != null) {
934 csToUse = this.nativeJdbcExtractor.getNativeCallableStatement(cs);
935 }
936 Object result = action.doInCallableStatement(csToUse);
937 handleWarnings(cs);
938 return result;
939 }
940 catch (SQLException ex) {
941 // Release Connection early, to avoid potential connection pool deadlock
942 // in the case when the exception translator hasn't been initialized yet.
943 if (csc instanceof ParameterDisposer) {
944 ((ParameterDisposer) csc).cleanupParameters();
945 }
946 String sql = getSql(csc);
947 csc = null;
948 JdbcUtils.closeStatement(cs);
949 cs = null;
950 DataSourceUtils.releaseConnection(con, getDataSource());
951 con = null;
952 throw getExceptionTranslator().translate("CallableStatementCallback", sql, ex);
953 }
954 finally {
955 if (csc instanceof ParameterDisposer) {
956 ((ParameterDisposer) csc).cleanupParameters();
957 }
958 JdbcUtils.closeStatement(cs);
959 DataSourceUtils.releaseConnection(con, getDataSource());
960 }
961 }
962
963 public Object execute(String callString, CallableStatementCallback action) throws DataAccessException {
964 return execute(new SimpleCallableStatementCreator(callString), action);
965 }
966
967 public Map call(CallableStatementCreator csc, List declaredParameters) throws DataAccessException {
968 final List updateCountParameters = new ArrayList();
969 final List resultSetParameters = new ArrayList();
970 final List callParameters = new ArrayList();
971 for (int i = 0; i < declaredParameters.size(); i++) {
972 SqlParameter parameter = (SqlParameter) declaredParameters.get(i);
973 if (parameter.isResultsParameter()) {
974 if (parameter instanceof SqlReturnResultSet) {
975 resultSetParameters.add(parameter);
976 }
977 else {
978 updateCountParameters.add(parameter);
979 }
980 }
981 else {
982 callParameters.add(parameter);
983 }
984 }
985 return (Map) execute(csc, new CallableStatementCallback() {
986 public Object doInCallableStatement(CallableStatement cs) throws SQLException {
987 boolean retVal = cs.execute();
988 int updateCount = cs.getUpdateCount();
989 if (logger.isDebugEnabled()) {
990 logger.debug("CallableStatement.execute() returned '" + retVal + "'");
991 logger.debug("CallableStatement.getUpdateCount() returned " + updateCount);
992 }
993 Map returnedResults = createResultsMap();
994 if (retVal || updateCount != -1) {
995 returnedResults.putAll(extractReturnedResults(cs, updateCountParameters, resultSetParameters, updateCount));
996 }
997 returnedResults.putAll(extractOutputParameters(cs, callParameters));
998 return returnedResults;
999 }
1000 });
1001 }
1002
1003 /**
1004 * Extract returned ResultSets from the completed stored procedure.
1005 * @param cs JDBC wrapper for the stored procedure
1006 * @param updateCountParameters Parameter list of declared update count parameters for the stored procedure
1007 * @param resultSetParameters Parameter list of declared resturn resultSet parameters for the stored procedure
1008 * @return Map that contains returned results
1009 */
1010 protected Map extractReturnedResults(
1011 CallableStatement cs, List updateCountParameters, List resultSetParameters, int updateCount)
1012 throws SQLException {
1013
1014 Map returnedResults = new HashMap();
1015 int rsIndex = 0;
1016 int updateIndex = 0;
1017 boolean moreResults;
1018 if (!skipResultsProcessing) {
1019 do {
1020 if (updateCount == -1) {
1021 if (resultSetParameters != null && resultSetParameters.size() > rsIndex) {
1022 SqlReturnResultSet declaredRsParam = (SqlReturnResultSet)resultSetParameters.get(rsIndex);
1023 returnedResults.putAll(processResultSet(cs.getResultSet(), declaredRsParam));
1024 rsIndex++;
1025 }
1026 else {
1027 if (!skipUndeclaredResults) {
1028 String rsName = RETURN_RESULT_SET_PREFIX + (rsIndex + 1);
1029 SqlReturnResultSet undeclaredRsParam = new SqlReturnResultSet(rsName, new ColumnMapRowMapper());
1030 logger.info("Added default SqlReturnResultSet parameter named " + rsName);
1031 returnedResults.putAll(processResultSet(cs.getResultSet(), undeclaredRsParam));
1032 rsIndex++;
1033 }
1034 }
1035 }
1036 else {
1037 if (updateCountParameters != null && updateCountParameters.size() > updateIndex) {
1038 SqlReturnUpdateCount ucParam = (SqlReturnUpdateCount)updateCountParameters.get(updateIndex);
1039 String declaredUcName = ucParam.getName();
1040 returnedResults.put(declaredUcName, new Integer(updateCount));
1041 updateIndex++;
1042 }
1043 else {
1044 if (!skipUndeclaredResults) {
1045 String undeclaredUcName = RETURN_UPDATE_COUNT_PREFIX + (updateIndex + 1);
1046 logger.info("Added default SqlReturnUpdateCount parameter named " + undeclaredUcName);
1047 returnedResults.put(undeclaredUcName, new Integer(updateCount));
1048 updateIndex++;
1049 }
1050 }
1051 }
1052 moreResults = cs.getMoreResults();
1053 updateCount = cs.getUpdateCount();
1054 if (logger.isDebugEnabled()) {
1055 logger.debug("CallableStatement.getUpdateCount() returned " + updateCount);
1056 }
1057 }
1058 while (moreResults || updateCount != -1);
1059 }
1060 return returnedResults;
1061 }
1062
1063 /**
1064 * Extract output parameters from the completed stored procedure.
1065 * @param cs JDBC wrapper for the stored procedure
1066 * @param parameters parameter list for the stored procedure
1067 * @return Map that contains returned results
1068 */
1069 protected Map extractOutputParameters(CallableStatement cs, List parameters) throws SQLException {
1070 Map returnedResults = new HashMap();
1071 int sqlColIndex = 1;
1072 for (int i = 0; i < parameters.size(); i++) {
1073 SqlParameter param = (SqlParameter) parameters.get(i);
1074 if (param instanceof SqlOutParameter) {
1075 SqlOutParameter outParam = (SqlOutParameter) param;
1076 if (outParam.isReturnTypeSupported()) {
1077 Object out = outParam.getSqlReturnType().getTypeValue(
1078 cs, sqlColIndex, outParam.getSqlType(), outParam.getTypeName());
1079 returnedResults.put(outParam.getName(), out);
1080 }
1081 else {
1082 Object out = cs.getObject(sqlColIndex);
1083 if (out instanceof ResultSet) {
1084 if (outParam.isResultSetSupported()) {
1085 returnedResults.putAll(processResultSet((ResultSet) out, outParam));
1086 }
1087 else {
1088 String rsName = outParam.getName();
1089 SqlReturnResultSet rsParam = new SqlReturnResultSet(rsName, new ColumnMapRowMapper());
1090 returnedResults.putAll(processResultSet(cs.getResultSet(), rsParam));
1091 logger.info("Added default SqlReturnResultSet parameter named " + rsName);
1092 }
1093 }
1094 else {
1095 returnedResults.put(outParam.getName(), out);
1096 }
1097 }
1098 }
1099 if (!(param.isResultsParameter())) {
1100 sqlColIndex++;
1101 }
1102 }
1103 return returnedResults;
1104 }
1105
1106 /**
1107 * Process the given ResultSet from a stored procedure.
1108 * @param rs the ResultSet to process
1109 * @param param the corresponding stored procedure parameter
1110 * @return Map that contains returned results
1111 */
1112 protected Map processResultSet(ResultSet rs, ResultSetSupportingSqlParameter param) throws SQLException {
1113 if (rs == null) {
1114 return Collections.EMPTY_MAP;
1115 }
1116 Map returnedResults = new HashMap();
1117 try {
1118 ResultSet rsToUse = rs;
1119 if (this.nativeJdbcExtractor != null) {
1120 rsToUse = this.nativeJdbcExtractor.getNativeResultSet(rs);
1121 }
1122 if (param.getRowMapper() != null) {
1123 RowMapper rowMapper = param.getRowMapper();
1124 Object result = (new RowMapperResultSetExtractor(rowMapper)).extractData(rsToUse);
1125 returnedResults.put(param.getName(), result);
1126 }
1127 else if (param.getRowCallbackHandler() != null) {
1128 RowCallbackHandler rch = param.getRowCallbackHandler();
1129 (new RowCallbackHandlerResultSetExtractor(rch)).extractData(rsToUse);
1130 returnedResults.put(param.getName(), "ResultSet returned from stored procedure was processed");
1131 }
1132 else if (param.getResultSetExtractor() != null) {
1133 Object result = param.getResultSetExtractor().extractData(rsToUse);
1134 returnedResults.put(param.getName(), result);
1135 }
1136 }
1137 finally {
1138 JdbcUtils.closeResultSet(rs);
1139 }
1140 return returnedResults;
1141 }
1142
1143
1144 //-------------------------------------------------------------------------
1145 // Implementation hooks and helper methods
1146 //-------------------------------------------------------------------------
1147
1148 /**
1149 * Create a new RowMapper for reading columns as key-value pairs.
1150 * @return the RowMapper to use
1151 * @see ColumnMapRowMapper
1152 */
1153 protected RowMapper getColumnMapRowMapper() {
1154 return new ColumnMapRowMapper();
1155 }
1156
1157 /**
1158 * Create a new RowMapper for reading result objects from a single column.
1159 * @param requiredType the type that each result object is expected to match
1160 * @return the RowMapper to use
1161 * @see SingleColumnRowMapper
1162 */
1163 protected RowMapper getSingleColumnRowMapper(Class requiredType) {
1164 return new SingleColumnRowMapper(requiredType);
1165 }
1166
1167 /**
1168 * Create a Map instance to be used as results map.
1169 * <p>If "isResultsMapCaseInsensitive" has been set to true, a linked case-insensitive Map
1170 * will be created if possible, else a plain HashMap (see Spring's CollectionFactory).
1171 * @return the results Map instance
1172 * @see #setResultsMapCaseInsensitive
1173 * @see org.springframework.core.CollectionFactory#createLinkedCaseInsensitiveMapIfPossible
1174 */
1175 protected Map createResultsMap() {
1176 if (isResultsMapCaseInsensitive()) {
1177 return CollectionFactory.createLinkedCaseInsensitiveMapIfPossible(10);
1178 }
1179 else {
1180 return new LinkedHashMap();
1181 }
1182 }
1183
1184 /**
1185 * Prepare the given JDBC Statement (or PreparedStatement or CallableStatement),
1186 * applying statement settings such as fetch size, max rows, and query timeout.
1187 * @param stmt the JDBC Statement to prepare
1188 * @throws SQLException if thrown by JDBC API
1189 * @see #setFetchSize
1190 * @see #setMaxRows
1191 * @see #setQueryTimeout
1192 * @see org.springframework.jdbc.datasource.DataSourceUtils#applyTransactionTimeout
1193 */
1194 protected void applyStatementSettings(Statement stmt) throws SQLException {
1195 int fetchSize = getFetchSize();
1196 if (fetchSize > 0) {
1197 stmt.setFetchSize(fetchSize);
1198 }
1199 int maxRows = getMaxRows();
1200 if (maxRows > 0) {
1201 stmt.setMaxRows(maxRows);
1202 }
1203 DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());
1204 }
1205
1206 /**
1207 * Throw an SQLWarningException if we're not ignoring warnings,
1208 * else log the warnings (at debug level).
1209 * @param stmt the current JDBC statement
1210 * @throws SQLWarningException if not ignoring warnings
1211 * @see org.springframework.jdbc.SQLWarningException
1212 */
1213 protected void handleWarnings(Statement stmt) throws SQLException {
1214 if (isIgnoreWarnings()) {
1215 if (logger.isDebugEnabled()) {
1216 SQLWarning warningToLog = stmt.getWarnings();
1217 while (warningToLog != null) {
1218 logger.debug("SQLWarning ignored: SQL state '" + warningToLog.getSQLState() + "', error code '" +
1219 warningToLog.getErrorCode() + "', message [" + warningToLog.getMessage() + "]");
1220 warningToLog = warningToLog.getNextWarning();
1221 }
1222 }
1223 }
1224 else {
1225 handleWarnings(stmt.getWarnings());
1226 }
1227 }
1228
1229 /**
1230 * Throw an SQLWarningException if encountering an actual warning.
1231 * @param warning the warnings object from the current statement.
1232 * May be <code>null</code>, in which case this method does nothing.
1233 * @throws SQLWarningException in case of an actual warning to be raised
1234 */
1235 protected void handleWarnings(SQLWarning warning) throws SQLWarningException {
1236 if (warning != null) {
1237 throw new SQLWarningException("Warning not ignored", warning);
1238 }
1239 }
1240
1241 /**
1242 * Determine SQL from potential provider object.
1243 * @param sqlProvider object that's potentially a SqlProvider
1244 * @return the SQL string, or <code>null</code>
1245 * @see SqlProvider
1246 */
1247 private static String getSql(Object sqlProvider) {
1248 if (sqlProvider instanceof SqlProvider) {
1249 return ((SqlProvider) sqlProvider).getSql();
1250 }
1251 else {
1252 return null;
1253 }
1254 }
1255
1256
1257 /**
1258 * Invocation handler that suppresses close calls on JDBC COnnections.
1259 * Also prepares returned Statement (Prepared/CallbackStatement) objects.
1260 * @see java.sql.Connection#close()
1261 */
1262 private class CloseSuppressingInvocationHandler implements InvocationHandler {
1263
1264 private final Connection target;
1265
1266 public CloseSuppressingInvocationHandler(Connection target) {
1267 this.target = target;
1268 }
1269
1270 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
1271 // Invocation on ConnectionProxy interface coming in...
1272
1273 if (method.getName().equals("getTargetConnection")) {
1274 // Handle getTargetConnection method: return underlying Connection.
1275 return this.target;
1276 }
1277 else if (method.getName().equals("equals")) {
1278 // Only consider equal when proxies are identical.
1279 return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
1280 }
1281 else if (method.getName().equals("hashCode")) {
1282 // Use hashCode of PersistenceManager proxy.
1283 return new Integer(System.identityHashCode(proxy));
1284 }
1285 else if (method.getName().equals("close")) {
1286 // Handle close method: suppress, not valid.
1287 return null;
1288 }
1289
1290 // Invoke method on target Connection.
1291 try {
1292 Object retVal = method.invoke(this.target, args);
1293
1294 // If return value is a JDBC Statement, apply statement settings
1295 // (fetch size, max rows, transaction timeout).
1296 if (retVal instanceof Statement) {
1297 applyStatementSettings(((Statement) retVal));
1298 }
1299
1300 return retVal;
1301 }
1302 catch (InvocationTargetException ex) {
1303 throw ex.getTargetException();
1304 }
1305 }
1306 }
1307
1308
1309 /**
1310 * Simple adapter for PreparedStatementCreator, allowing to use a plain SQL statement.
1311 */
1312 private static class SimplePreparedStatementCreator implements PreparedStatementCreator, SqlProvider {
1313
1314 private final String sql;
1315
1316 public SimplePreparedStatementCreator(String sql) {
1317 Assert.notNull(sql, "SQL must not be null");
1318 this.sql = sql;
1319 }
1320
1321 public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
1322 return con.prepareStatement(this.sql);
1323 }
1324
1325 public String getSql() {
1326 return this.sql;
1327 }
1328 }
1329
1330
1331 /**
1332 * Simple adapter for CallableStatementCreator, allowing to use a plain SQL statement.
1333 */
1334 private static class SimpleCallableStatementCreator implements CallableStatementCreator, SqlProvider {
1335
1336 private final String callString;
1337
1338 public SimpleCallableStatementCreator(String callString) {
1339 Assert.notNull(callString, "Call string must not be null");
1340 this.callString = callString;
1341 }
1342
1343 public CallableStatement createCallableStatement(Connection con) throws SQLException {
1344 return con.prepareCall(this.callString);
1345 }
1346
1347 public String getSql() {
1348 return this.callString;
1349 }
1350 }
1351
1352
1353 /**
1354 * Adapter to enable use of a RowCallbackHandler inside a ResultSetExtractor.
1355 * <p>Uses a regular ResultSet, so we have to be careful when using it:
1356 * We don't use it for navigating since this could lead to unpredictable consequences.
1357 */
1358 private static class RowCallbackHandlerResultSetExtractor implements ResultSetExtractor {
1359
1360 private final RowCallbackHandler rch;
1361
1362 public RowCallbackHandlerResultSetExtractor(RowCallbackHandler rch) {
1363 this.rch = rch;
1364 }
1365
1366 public Object extractData(ResultSet rs) throws SQLException {
1367 while (rs.next()) {
1368 this.rch.processRow(rs);
1369 }
1370 return null;
1371 }
1372 }
1373
1374 }