001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.jdbc.core; 018 019import java.lang.reflect.InvocationHandler; 020import java.lang.reflect.InvocationTargetException; 021import java.lang.reflect.Method; 022import java.lang.reflect.Proxy; 023import java.sql.BatchUpdateException; 024import java.sql.CallableStatement; 025import java.sql.Connection; 026import java.sql.PreparedStatement; 027import java.sql.ResultSet; 028import java.sql.SQLException; 029import java.sql.SQLWarning; 030import java.sql.Statement; 031import java.util.ArrayList; 032import java.util.Collection; 033import java.util.Collections; 034import java.util.LinkedHashMap; 035import java.util.List; 036import java.util.Map; 037 038import javax.sql.DataSource; 039 040import org.springframework.dao.DataAccessException; 041import org.springframework.dao.InvalidDataAccessApiUsageException; 042import org.springframework.dao.support.DataAccessUtils; 043import org.springframework.jdbc.SQLWarningException; 044import org.springframework.jdbc.UncategorizedSQLException; 045import org.springframework.jdbc.datasource.ConnectionProxy; 046import org.springframework.jdbc.datasource.DataSourceUtils; 047import org.springframework.jdbc.support.JdbcAccessor; 048import org.springframework.jdbc.support.JdbcUtils; 049import org.springframework.jdbc.support.KeyHolder; 050import org.springframework.jdbc.support.rowset.SqlRowSet; 051import org.springframework.lang.Nullable; 052import org.springframework.util.Assert; 053import org.springframework.util.LinkedCaseInsensitiveMap; 054import org.springframework.util.StringUtils; 055 056/** 057 * <b>This is the central class in the JDBC core package.</b> 058 * It simplifies the use of JDBC and helps to avoid common errors. 059 * It executes core JDBC workflow, leaving application code to provide SQL 060 * and extract results. This class executes SQL queries or updates, initiating 061 * iteration over ResultSets and catching JDBC exceptions and translating 062 * them to the generic, more informative exception hierarchy defined in the 063 * {@code org.springframework.dao} package. 064 * 065 * <p>Code using this class need only implement callback interfaces, giving 066 * them a clearly defined contract. The {@link PreparedStatementCreator} callback 067 * interface creates a prepared statement given a Connection, providing SQL and 068 * any necessary parameters. The {@link ResultSetExtractor} interface extracts 069 * values from a ResultSet. See also {@link PreparedStatementSetter} and 070 * {@link RowMapper} for two popular alternative callback interfaces. 071 * 072 * <p>Can be used within a service implementation via direct instantiation 073 * with a DataSource reference, or get prepared in an application context 074 * and given to services as bean reference. Note: The DataSource should 075 * always be configured as a bean in the application context, in the first case 076 * given to the service directly, in the second case to the prepared template. 077 * 078 * <p>Because this class is parameterizable by the callback interfaces and 079 * the {@link org.springframework.jdbc.support.SQLExceptionTranslator} 080 * interface, there should be no need to subclass it. 081 * 082 * <p>All SQL operations performed by this class are logged at debug level, 083 * using "org.springframework.jdbc.core.JdbcTemplate" as log category. 084 * 085 * <p><b>NOTE: An instance of this class is thread-safe once configured.</b> 086 * 087 * @author Rod Johnson 088 * @author Juergen Hoeller 089 * @author Thomas Risberg 090 * @since May 3, 2001 091 * @see PreparedStatementCreator 092 * @see PreparedStatementSetter 093 * @see CallableStatementCreator 094 * @see PreparedStatementCallback 095 * @see CallableStatementCallback 096 * @see ResultSetExtractor 097 * @see RowCallbackHandler 098 * @see RowMapper 099 * @see org.springframework.jdbc.support.SQLExceptionTranslator 100 */ 101public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { 102 103 private static final String RETURN_RESULT_SET_PREFIX = "#result-set-"; 104 105 private static final String RETURN_UPDATE_COUNT_PREFIX = "#update-count-"; 106 107 108 /** If this variable is false, we will throw exceptions on SQL warnings. */ 109 private boolean ignoreWarnings = true; 110 111 /** 112 * If this variable is set to a non-negative value, it will be used for setting the 113 * fetchSize property on statements used for query processing. 114 */ 115 private int fetchSize = -1; 116 117 /** 118 * If this variable is set to a non-negative value, it will be used for setting the 119 * maxRows property on statements used for query processing. 120 */ 121 private int maxRows = -1; 122 123 /** 124 * If this variable is set to a non-negative value, it will be used for setting the 125 * queryTimeout property on statements used for query processing. 126 */ 127 private int queryTimeout = -1; 128 129 /** 130 * If this variable is set to true, then all results checking will be bypassed for any 131 * callable statement processing. This can be used to avoid a bug in some older Oracle 132 * JDBC drivers like 10.1.0.2. 133 */ 134 private boolean skipResultsProcessing = false; 135 136 /** 137 * If this variable is set to true then all results from a stored procedure call 138 * that don't have a corresponding SqlOutParameter declaration will be bypassed. 139 * All other results processing will be take place unless the variable 140 * {@code skipResultsProcessing} is set to {@code true}. 141 */ 142 private boolean skipUndeclaredResults = false; 143 144 /** 145 * If this variable is set to true then execution of a CallableStatement will return 146 * the results in a Map that uses case insensitive names for the parameters. 147 */ 148 private boolean resultsMapCaseInsensitive = false; 149 150 151 /** 152 * Construct a new JdbcTemplate for bean usage. 153 * <p>Note: The DataSource has to be set before using the instance. 154 * @see #setDataSource 155 */ 156 public JdbcTemplate() { 157 } 158 159 /** 160 * Construct a new JdbcTemplate, given a DataSource to obtain connections from. 161 * <p>Note: This will not trigger initialization of the exception translator. 162 * @param dataSource the JDBC DataSource to obtain connections from 163 */ 164 public JdbcTemplate(DataSource dataSource) { 165 setDataSource(dataSource); 166 afterPropertiesSet(); 167 } 168 169 /** 170 * Construct a new JdbcTemplate, given a DataSource to obtain connections from. 171 * <p>Note: Depending on the "lazyInit" flag, initialization of the exception translator 172 * will be triggered. 173 * @param dataSource the JDBC DataSource to obtain connections from 174 * @param lazyInit whether to lazily initialize the SQLExceptionTranslator 175 */ 176 public JdbcTemplate(DataSource dataSource, boolean lazyInit) { 177 setDataSource(dataSource); 178 setLazyInit(lazyInit); 179 afterPropertiesSet(); 180 } 181 182 183 /** 184 * Set whether or not we want to ignore SQLWarnings. 185 * <p>Default is "true", swallowing and logging all warnings. Switch this flag 186 * to "false" to make the JdbcTemplate throw an SQLWarningException instead. 187 * @see java.sql.SQLWarning 188 * @see org.springframework.jdbc.SQLWarningException 189 * @see #handleWarnings 190 */ 191 public void setIgnoreWarnings(boolean ignoreWarnings) { 192 this.ignoreWarnings = ignoreWarnings; 193 } 194 195 /** 196 * Return whether or not we ignore SQLWarnings. 197 */ 198 public boolean isIgnoreWarnings() { 199 return this.ignoreWarnings; 200 } 201 202 /** 203 * Set the fetch size for this JdbcTemplate. This is important for processing large 204 * result sets: Setting this higher than the default value will increase processing 205 * speed at the cost of memory consumption; setting this lower can avoid transferring 206 * row data that will never be read by the application. 207 * <p>Default is -1, indicating to use the JDBC driver's default configuration 208 * (i.e. to not pass a specific fetch size setting on to the driver). 209 * <p>Note: As of 4.3, negative values other than -1 will get passed on to the 210 * driver, since e.g. MySQL supports special behavior for {@code Integer.MIN_VALUE}. 211 * @see java.sql.Statement#setFetchSize 212 */ 213 public void setFetchSize(int fetchSize) { 214 this.fetchSize = fetchSize; 215 } 216 217 /** 218 * Return the fetch size specified for this JdbcTemplate. 219 */ 220 public int getFetchSize() { 221 return this.fetchSize; 222 } 223 224 /** 225 * Set the maximum number of rows for this JdbcTemplate. This is important for 226 * processing subsets of large result sets, avoiding to read and hold the entire 227 * result set in the database or in the JDBC driver if we're never interested in 228 * the entire result in the first place (for example, when performing searches 229 * that might return a large number of matches). 230 * <p>Default is -1, indicating to use the JDBC driver's default configuration 231 * (i.e. to not pass a specific max rows setting on to the driver). 232 * <p>Note: As of 4.3, negative values other than -1 will get passed on to the 233 * driver, in sync with {@link #setFetchSize}'s support for special MySQL values. 234 * @see java.sql.Statement#setMaxRows 235 */ 236 public void setMaxRows(int maxRows) { 237 this.maxRows = maxRows; 238 } 239 240 /** 241 * Return the maximum number of rows specified for this JdbcTemplate. 242 */ 243 public int getMaxRows() { 244 return this.maxRows; 245 } 246 247 /** 248 * Set the query timeout for statements that this JdbcTemplate executes. 249 * <p>Default is -1, indicating to use the JDBC driver's default 250 * (i.e. to not pass a specific query timeout setting on the driver). 251 * <p>Note: Any timeout specified here will be overridden by the remaining 252 * transaction timeout when executing within a transaction that has a 253 * timeout specified at the transaction level. 254 * @see java.sql.Statement#setQueryTimeout 255 */ 256 public void setQueryTimeout(int queryTimeout) { 257 this.queryTimeout = queryTimeout; 258 } 259 260 /** 261 * Return the query timeout for statements that this JdbcTemplate executes. 262 */ 263 public int getQueryTimeout() { 264 return this.queryTimeout; 265 } 266 267 /** 268 * Set whether results processing should be skipped. Can be used to optimize callable 269 * statement processing when we know that no results are being passed back - the processing 270 * of out parameter will still take place. This can be used to avoid a bug in some older 271 * Oracle JDBC drivers like 10.1.0.2. 272 */ 273 public void setSkipResultsProcessing(boolean skipResultsProcessing) { 274 this.skipResultsProcessing = skipResultsProcessing; 275 } 276 277 /** 278 * Return whether results processing should be skipped. 279 */ 280 public boolean isSkipResultsProcessing() { 281 return this.skipResultsProcessing; 282 } 283 284 /** 285 * Set whether undeclared results should be skipped. 286 */ 287 public void setSkipUndeclaredResults(boolean skipUndeclaredResults) { 288 this.skipUndeclaredResults = skipUndeclaredResults; 289 } 290 291 /** 292 * Return whether undeclared results should be skipped. 293 */ 294 public boolean isSkipUndeclaredResults() { 295 return this.skipUndeclaredResults; 296 } 297 298 /** 299 * Set whether execution of a CallableStatement will return the results in a Map 300 * that uses case insensitive names for the parameters. 301 */ 302 public void setResultsMapCaseInsensitive(boolean resultsMapCaseInsensitive) { 303 this.resultsMapCaseInsensitive = resultsMapCaseInsensitive; 304 } 305 306 /** 307 * Return whether execution of a CallableStatement will return the results in a Map 308 * that uses case insensitive names for the parameters. 309 */ 310 public boolean isResultsMapCaseInsensitive() { 311 return this.resultsMapCaseInsensitive; 312 } 313 314 315 //------------------------------------------------------------------------- 316 // Methods dealing with a plain java.sql.Connection 317 //------------------------------------------------------------------------- 318 319 @Override 320 @Nullable 321 public <T> T execute(ConnectionCallback<T> action) throws DataAccessException { 322 Assert.notNull(action, "Callback object must not be null"); 323 324 Connection con = DataSourceUtils.getConnection(obtainDataSource()); 325 try { 326 // Create close-suppressing Connection proxy, also preparing returned Statements. 327 Connection conToUse = createConnectionProxy(con); 328 return action.doInConnection(conToUse); 329 } 330 catch (SQLException ex) { 331 // Release Connection early, to avoid potential connection pool deadlock 332 // in the case when the exception translator hasn't been initialized yet. 333 String sql = getSql(action); 334 DataSourceUtils.releaseConnection(con, getDataSource()); 335 con = null; 336 throw translateException("ConnectionCallback", sql, ex); 337 } 338 finally { 339 DataSourceUtils.releaseConnection(con, getDataSource()); 340 } 341 } 342 343 /** 344 * Create a close-suppressing proxy for the given JDBC Connection. 345 * Called by the {@code execute} method. 346 * <p>The proxy also prepares returned JDBC Statements, applying 347 * statement settings such as fetch size, max rows, and query timeout. 348 * @param con the JDBC Connection to create a proxy for 349 * @return the Connection proxy 350 * @see java.sql.Connection#close() 351 * @see #execute(ConnectionCallback) 352 * @see #applyStatementSettings 353 */ 354 protected Connection createConnectionProxy(Connection con) { 355 return (Connection) Proxy.newProxyInstance( 356 ConnectionProxy.class.getClassLoader(), 357 new Class<?>[] {ConnectionProxy.class}, 358 new CloseSuppressingInvocationHandler(con)); 359 } 360 361 362 //------------------------------------------------------------------------- 363 // Methods dealing with static SQL (java.sql.Statement) 364 //------------------------------------------------------------------------- 365 366 @Override 367 @Nullable 368 public <T> T execute(StatementCallback<T> action) throws DataAccessException { 369 Assert.notNull(action, "Callback object must not be null"); 370 371 Connection con = DataSourceUtils.getConnection(obtainDataSource()); 372 Statement stmt = null; 373 try { 374 stmt = con.createStatement(); 375 applyStatementSettings(stmt); 376 T result = action.doInStatement(stmt); 377 handleWarnings(stmt); 378 return result; 379 } 380 catch (SQLException ex) { 381 // Release Connection early, to avoid potential connection pool deadlock 382 // in the case when the exception translator hasn't been initialized yet. 383 String sql = getSql(action); 384 JdbcUtils.closeStatement(stmt); 385 stmt = null; 386 DataSourceUtils.releaseConnection(con, getDataSource()); 387 con = null; 388 throw translateException("StatementCallback", sql, ex); 389 } 390 finally { 391 JdbcUtils.closeStatement(stmt); 392 DataSourceUtils.releaseConnection(con, getDataSource()); 393 } 394 } 395 396 @Override 397 public void execute(final String sql) throws DataAccessException { 398 if (logger.isDebugEnabled()) { 399 logger.debug("Executing SQL statement [" + sql + "]"); 400 } 401 402 /** 403 * Callback to execute the statement. 404 */ 405 class ExecuteStatementCallback implements StatementCallback<Object>, SqlProvider { 406 @Override 407 @Nullable 408 public Object doInStatement(Statement stmt) throws SQLException { 409 stmt.execute(sql); 410 return null; 411 } 412 @Override 413 public String getSql() { 414 return sql; 415 } 416 } 417 418 execute(new ExecuteStatementCallback()); 419 } 420 421 @Override 422 @Nullable 423 public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException { 424 Assert.notNull(sql, "SQL must not be null"); 425 Assert.notNull(rse, "ResultSetExtractor must not be null"); 426 if (logger.isDebugEnabled()) { 427 logger.debug("Executing SQL query [" + sql + "]"); 428 } 429 430 /** 431 * Callback to execute the query. 432 */ 433 class QueryStatementCallback implements StatementCallback<T>, SqlProvider { 434 @Override 435 @Nullable 436 public T doInStatement(Statement stmt) throws SQLException { 437 ResultSet rs = null; 438 try { 439 rs = stmt.executeQuery(sql); 440 return rse.extractData(rs); 441 } 442 finally { 443 JdbcUtils.closeResultSet(rs); 444 } 445 } 446 @Override 447 public String getSql() { 448 return sql; 449 } 450 } 451 452 return execute(new QueryStatementCallback()); 453 } 454 455 @Override 456 public void query(String sql, RowCallbackHandler rch) throws DataAccessException { 457 query(sql, new RowCallbackHandlerResultSetExtractor(rch)); 458 } 459 460 @Override 461 public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException { 462 return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper))); 463 } 464 465 @Override 466 public Map<String, Object> queryForMap(String sql) throws DataAccessException { 467 return result(queryForObject(sql, getColumnMapRowMapper())); 468 } 469 470 @Override 471 @Nullable 472 public <T> T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException { 473 List<T> results = query(sql, rowMapper); 474 return DataAccessUtils.nullableSingleResult(results); 475 } 476 477 @Override 478 @Nullable 479 public <T> T queryForObject(String sql, Class<T> requiredType) throws DataAccessException { 480 return queryForObject(sql, getSingleColumnRowMapper(requiredType)); 481 } 482 483 @Override 484 public <T> List<T> queryForList(String sql, Class<T> elementType) throws DataAccessException { 485 return query(sql, getSingleColumnRowMapper(elementType)); 486 } 487 488 @Override 489 public List<Map<String, Object>> queryForList(String sql) throws DataAccessException { 490 return query(sql, getColumnMapRowMapper()); 491 } 492 493 @Override 494 public SqlRowSet queryForRowSet(String sql) throws DataAccessException { 495 return result(query(sql, new SqlRowSetResultSetExtractor())); 496 } 497 498 @Override 499 public int update(final String sql) throws DataAccessException { 500 Assert.notNull(sql, "SQL must not be null"); 501 if (logger.isDebugEnabled()) { 502 logger.debug("Executing SQL update [" + sql + "]"); 503 } 504 505 /** 506 * Callback to execute the update statement. 507 */ 508 class UpdateStatementCallback implements StatementCallback<Integer>, SqlProvider { 509 @Override 510 public Integer doInStatement(Statement stmt) throws SQLException { 511 int rows = stmt.executeUpdate(sql); 512 if (logger.isTraceEnabled()) { 513 logger.trace("SQL update affected " + rows + " rows"); 514 } 515 return rows; 516 } 517 @Override 518 public String getSql() { 519 return sql; 520 } 521 } 522 523 return updateCount(execute(new UpdateStatementCallback())); 524 } 525 526 @Override 527 public int[] batchUpdate(final String... sql) throws DataAccessException { 528 Assert.notEmpty(sql, "SQL array must not be empty"); 529 if (logger.isDebugEnabled()) { 530 logger.debug("Executing SQL batch update of " + sql.length + " statements"); 531 } 532 533 /** 534 * Callback to execute the batch update. 535 */ 536 class BatchUpdateStatementCallback implements StatementCallback<int[]>, SqlProvider { 537 538 @Nullable 539 private String currSql; 540 541 @Override 542 public int[] doInStatement(Statement stmt) throws SQLException, DataAccessException { 543 int[] rowsAffected = new int[sql.length]; 544 if (JdbcUtils.supportsBatchUpdates(stmt.getConnection())) { 545 for (String sqlStmt : sql) { 546 this.currSql = appendSql(this.currSql, sqlStmt); 547 stmt.addBatch(sqlStmt); 548 } 549 try { 550 rowsAffected = stmt.executeBatch(); 551 } 552 catch (BatchUpdateException ex) { 553 String batchExceptionSql = null; 554 for (int i = 0; i < ex.getUpdateCounts().length; i++) { 555 if (ex.getUpdateCounts()[i] == Statement.EXECUTE_FAILED) { 556 batchExceptionSql = appendSql(batchExceptionSql, sql[i]); 557 } 558 } 559 if (StringUtils.hasLength(batchExceptionSql)) { 560 this.currSql = batchExceptionSql; 561 } 562 throw ex; 563 } 564 } 565 else { 566 for (int i = 0; i < sql.length; i++) { 567 this.currSql = sql[i]; 568 if (!stmt.execute(sql[i])) { 569 rowsAffected[i] = stmt.getUpdateCount(); 570 } 571 else { 572 throw new InvalidDataAccessApiUsageException("Invalid batch SQL statement: " + sql[i]); 573 } 574 } 575 } 576 return rowsAffected; 577 } 578 579 private String appendSql(@Nullable String sql, String statement) { 580 return (StringUtils.hasLength(sql) ? sql + "; " + statement : statement); 581 } 582 583 @Override 584 @Nullable 585 public String getSql() { 586 return this.currSql; 587 } 588 } 589 590 int[] result = execute(new BatchUpdateStatementCallback()); 591 Assert.state(result != null, "No update counts"); 592 return result; 593 } 594 595 596 //------------------------------------------------------------------------- 597 // Methods dealing with prepared statements 598 //------------------------------------------------------------------------- 599 600 @Override 601 @Nullable 602 public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) 603 throws DataAccessException { 604 605 Assert.notNull(psc, "PreparedStatementCreator must not be null"); 606 Assert.notNull(action, "Callback object must not be null"); 607 if (logger.isDebugEnabled()) { 608 String sql = getSql(psc); 609 logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : "")); 610 } 611 612 Connection con = DataSourceUtils.getConnection(obtainDataSource()); 613 PreparedStatement ps = null; 614 try { 615 ps = psc.createPreparedStatement(con); 616 applyStatementSettings(ps); 617 T result = action.doInPreparedStatement(ps); 618 handleWarnings(ps); 619 return result; 620 } 621 catch (SQLException ex) { 622 // Release Connection early, to avoid potential connection pool deadlock 623 // in the case when the exception translator hasn't been initialized yet. 624 if (psc instanceof ParameterDisposer) { 625 ((ParameterDisposer) psc).cleanupParameters(); 626 } 627 String sql = getSql(psc); 628 psc = null; 629 JdbcUtils.closeStatement(ps); 630 ps = null; 631 DataSourceUtils.releaseConnection(con, getDataSource()); 632 con = null; 633 throw translateException("PreparedStatementCallback", sql, ex); 634 } 635 finally { 636 if (psc instanceof ParameterDisposer) { 637 ((ParameterDisposer) psc).cleanupParameters(); 638 } 639 JdbcUtils.closeStatement(ps); 640 DataSourceUtils.releaseConnection(con, getDataSource()); 641 } 642 } 643 644 @Override 645 @Nullable 646 public <T> T execute(String sql, PreparedStatementCallback<T> action) throws DataAccessException { 647 return execute(new SimplePreparedStatementCreator(sql), action); 648 } 649 650 /** 651 * Query using a prepared statement, allowing for a PreparedStatementCreator 652 * and a PreparedStatementSetter. Most other query methods use this method, 653 * but application code will always work with either a creator or a setter. 654 * @param psc a callback that creates a PreparedStatement given a Connection 655 * @param pss a callback that knows how to set values on the prepared statement. 656 * If this is {@code null}, the SQL will be assumed to contain no bind parameters. 657 * @param rse a callback that will extract results 658 * @return an arbitrary result object, as returned by the ResultSetExtractor 659 * @throws DataAccessException if there is any problem 660 */ 661 @Nullable 662 public <T> T query( 663 PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss, final ResultSetExtractor<T> rse) 664 throws DataAccessException { 665 666 Assert.notNull(rse, "ResultSetExtractor must not be null"); 667 logger.debug("Executing prepared SQL query"); 668 669 return execute(psc, new PreparedStatementCallback<T>() { 670 @Override 671 @Nullable 672 public T doInPreparedStatement(PreparedStatement ps) throws SQLException { 673 ResultSet rs = null; 674 try { 675 if (pss != null) { 676 pss.setValues(ps); 677 } 678 rs = ps.executeQuery(); 679 return rse.extractData(rs); 680 } 681 finally { 682 JdbcUtils.closeResultSet(rs); 683 if (pss instanceof ParameterDisposer) { 684 ((ParameterDisposer) pss).cleanupParameters(); 685 } 686 } 687 } 688 }); 689 } 690 691 @Override 692 @Nullable 693 public <T> T query(PreparedStatementCreator psc, ResultSetExtractor<T> rse) throws DataAccessException { 694 return query(psc, null, rse); 695 } 696 697 @Override 698 @Nullable 699 public <T> T query(String sql, @Nullable PreparedStatementSetter pss, ResultSetExtractor<T> rse) throws DataAccessException { 700 return query(new SimplePreparedStatementCreator(sql), pss, rse); 701 } 702 703 @Override 704 @Nullable 705 public <T> T query(String sql, Object[] args, int[] argTypes, ResultSetExtractor<T> rse) throws DataAccessException { 706 return query(sql, newArgTypePreparedStatementSetter(args, argTypes), rse); 707 } 708 709 @Override 710 @Nullable 711 public <T> T query(String sql, @Nullable Object[] args, ResultSetExtractor<T> rse) throws DataAccessException { 712 return query(sql, newArgPreparedStatementSetter(args), rse); 713 } 714 715 @Override 716 @Nullable 717 public <T> T query(String sql, ResultSetExtractor<T> rse, @Nullable Object... args) throws DataAccessException { 718 return query(sql, newArgPreparedStatementSetter(args), rse); 719 } 720 721 @Override 722 public void query(PreparedStatementCreator psc, RowCallbackHandler rch) throws DataAccessException { 723 query(psc, new RowCallbackHandlerResultSetExtractor(rch)); 724 } 725 726 @Override 727 public void query(String sql, @Nullable PreparedStatementSetter pss, RowCallbackHandler rch) throws DataAccessException { 728 query(sql, pss, new RowCallbackHandlerResultSetExtractor(rch)); 729 } 730 731 @Override 732 public void query(String sql, Object[] args, int[] argTypes, RowCallbackHandler rch) throws DataAccessException { 733 query(sql, newArgTypePreparedStatementSetter(args, argTypes), rch); 734 } 735 736 @Override 737 public void query(String sql, @Nullable Object[] args, RowCallbackHandler rch) throws DataAccessException { 738 query(sql, newArgPreparedStatementSetter(args), rch); 739 } 740 741 @Override 742 public void query(String sql, RowCallbackHandler rch, @Nullable Object... args) throws DataAccessException { 743 query(sql, newArgPreparedStatementSetter(args), rch); 744 } 745 746 @Override 747 public <T> List<T> query(PreparedStatementCreator psc, RowMapper<T> rowMapper) throws DataAccessException { 748 return result(query(psc, new RowMapperResultSetExtractor<>(rowMapper))); 749 } 750 751 @Override 752 public <T> List<T> query(String sql, @Nullable PreparedStatementSetter pss, RowMapper<T> rowMapper) throws DataAccessException { 753 return result(query(sql, pss, new RowMapperResultSetExtractor<>(rowMapper))); 754 } 755 756 @Override 757 public <T> List<T> query(String sql, Object[] args, int[] argTypes, RowMapper<T> rowMapper) throws DataAccessException { 758 return result(query(sql, args, argTypes, new RowMapperResultSetExtractor<>(rowMapper))); 759 } 760 761 @Override 762 public <T> List<T> query(String sql, @Nullable Object[] args, RowMapper<T> rowMapper) throws DataAccessException { 763 return result(query(sql, args, new RowMapperResultSetExtractor<>(rowMapper))); 764 } 765 766 @Override 767 public <T> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object... args) throws DataAccessException { 768 return result(query(sql, args, new RowMapperResultSetExtractor<>(rowMapper))); 769 } 770 771 @Override 772 @Nullable 773 public <T> T queryForObject(String sql, Object[] args, int[] argTypes, RowMapper<T> rowMapper) 774 throws DataAccessException { 775 776 List<T> results = query(sql, args, argTypes, new RowMapperResultSetExtractor<>(rowMapper, 1)); 777 return DataAccessUtils.nullableSingleResult(results); 778 } 779 780 @Override 781 @Nullable 782 public <T> T queryForObject(String sql, @Nullable Object[] args, RowMapper<T> rowMapper) throws DataAccessException { 783 List<T> results = query(sql, args, new RowMapperResultSetExtractor<>(rowMapper, 1)); 784 return DataAccessUtils.nullableSingleResult(results); 785 } 786 787 @Override 788 @Nullable 789 public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args) throws DataAccessException { 790 List<T> results = query(sql, args, new RowMapperResultSetExtractor<>(rowMapper, 1)); 791 return DataAccessUtils.nullableSingleResult(results); 792 } 793 794 @Override 795 @Nullable 796 public <T> T queryForObject(String sql, Object[] args, int[] argTypes, Class<T> requiredType) 797 throws DataAccessException { 798 799 return queryForObject(sql, args, argTypes, getSingleColumnRowMapper(requiredType)); 800 } 801 802 @Override 803 public <T> T queryForObject(String sql, @Nullable Object[] args, Class<T> requiredType) throws DataAccessException { 804 return queryForObject(sql, args, getSingleColumnRowMapper(requiredType)); 805 } 806 807 @Override 808 public <T> T queryForObject(String sql, Class<T> requiredType, @Nullable Object... args) throws DataAccessException { 809 return queryForObject(sql, args, getSingleColumnRowMapper(requiredType)); 810 } 811 812 @Override 813 public Map<String, Object> queryForMap(String sql, Object[] args, int[] argTypes) throws DataAccessException { 814 return result(queryForObject(sql, args, argTypes, getColumnMapRowMapper())); 815 } 816 817 @Override 818 public Map<String, Object> queryForMap(String sql, @Nullable Object... args) throws DataAccessException { 819 return result(queryForObject(sql, args, getColumnMapRowMapper())); 820 } 821 822 @Override 823 public <T> List<T> queryForList(String sql, Object[] args, int[] argTypes, Class<T> elementType) throws DataAccessException { 824 return query(sql, args, argTypes, getSingleColumnRowMapper(elementType)); 825 } 826 827 @Override 828 public <T> List<T> queryForList(String sql, @Nullable Object[] args, Class<T> elementType) throws DataAccessException { 829 return query(sql, args, getSingleColumnRowMapper(elementType)); 830 } 831 832 @Override 833 public <T> List<T> queryForList(String sql, Class<T> elementType, @Nullable Object... args) throws DataAccessException { 834 return query(sql, args, getSingleColumnRowMapper(elementType)); 835 } 836 837 @Override 838 public List<Map<String, Object>> queryForList(String sql, Object[] args, int[] argTypes) throws DataAccessException { 839 return query(sql, args, argTypes, getColumnMapRowMapper()); 840 } 841 842 @Override 843 public List<Map<String, Object>> queryForList(String sql, @Nullable Object... args) throws DataAccessException { 844 return query(sql, args, getColumnMapRowMapper()); 845 } 846 847 @Override 848 public SqlRowSet queryForRowSet(String sql, Object[] args, int[] argTypes) throws DataAccessException { 849 return result(query(sql, args, argTypes, new SqlRowSetResultSetExtractor())); 850 } 851 852 @Override 853 public SqlRowSet queryForRowSet(String sql, @Nullable Object... args) throws DataAccessException { 854 return result(query(sql, args, new SqlRowSetResultSetExtractor())); 855 } 856 857 protected int update(final PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss) 858 throws DataAccessException { 859 860 logger.debug("Executing prepared SQL update"); 861 862 return updateCount(execute(psc, ps -> { 863 try { 864 if (pss != null) { 865 pss.setValues(ps); 866 } 867 int rows = ps.executeUpdate(); 868 if (logger.isTraceEnabled()) { 869 logger.trace("SQL update affected " + rows + " rows"); 870 } 871 return rows; 872 } 873 finally { 874 if (pss instanceof ParameterDisposer) { 875 ((ParameterDisposer) pss).cleanupParameters(); 876 } 877 } 878 })); 879 } 880 881 @Override 882 public int update(PreparedStatementCreator psc) throws DataAccessException { 883 return update(psc, (PreparedStatementSetter) null); 884 } 885 886 @Override 887 public int update(final PreparedStatementCreator psc, final KeyHolder generatedKeyHolder) 888 throws DataAccessException { 889 890 Assert.notNull(generatedKeyHolder, "KeyHolder must not be null"); 891 logger.debug("Executing SQL update and returning generated keys"); 892 893 return updateCount(execute(psc, ps -> { 894 int rows = ps.executeUpdate(); 895 List<Map<String, Object>> generatedKeys = generatedKeyHolder.getKeyList(); 896 generatedKeys.clear(); 897 ResultSet keys = ps.getGeneratedKeys(); 898 if (keys != null) { 899 try { 900 RowMapperResultSetExtractor<Map<String, Object>> rse = 901 new RowMapperResultSetExtractor<>(getColumnMapRowMapper(), 1); 902 generatedKeys.addAll(result(rse.extractData(keys))); 903 } 904 finally { 905 JdbcUtils.closeResultSet(keys); 906 } 907 } 908 if (logger.isTraceEnabled()) { 909 logger.trace("SQL update affected " + rows + " rows and returned " + generatedKeys.size() + " keys"); 910 } 911 return rows; 912 })); 913 } 914 915 @Override 916 public int update(String sql, @Nullable PreparedStatementSetter pss) throws DataAccessException { 917 return update(new SimplePreparedStatementCreator(sql), pss); 918 } 919 920 @Override 921 public int update(String sql, Object[] args, int[] argTypes) throws DataAccessException { 922 return update(sql, newArgTypePreparedStatementSetter(args, argTypes)); 923 } 924 925 @Override 926 public int update(String sql, @Nullable Object... args) throws DataAccessException { 927 return update(sql, newArgPreparedStatementSetter(args)); 928 } 929 930 @Override 931 public int[] batchUpdate(String sql, final BatchPreparedStatementSetter pss) throws DataAccessException { 932 if (logger.isDebugEnabled()) { 933 logger.debug("Executing SQL batch update [" + sql + "]"); 934 } 935 936 int[] result = execute(sql, (PreparedStatementCallback<int[]>) ps -> { 937 try { 938 int batchSize = pss.getBatchSize(); 939 InterruptibleBatchPreparedStatementSetter ipss = 940 (pss instanceof InterruptibleBatchPreparedStatementSetter ? 941 (InterruptibleBatchPreparedStatementSetter) pss : null); 942 if (JdbcUtils.supportsBatchUpdates(ps.getConnection())) { 943 for (int i = 0; i < batchSize; i++) { 944 pss.setValues(ps, i); 945 if (ipss != null && ipss.isBatchExhausted(i)) { 946 break; 947 } 948 ps.addBatch(); 949 } 950 return ps.executeBatch(); 951 } 952 else { 953 List<Integer> rowsAffected = new ArrayList<>(); 954 for (int i = 0; i < batchSize; i++) { 955 pss.setValues(ps, i); 956 if (ipss != null && ipss.isBatchExhausted(i)) { 957 break; 958 } 959 rowsAffected.add(ps.executeUpdate()); 960 } 961 int[] rowsAffectedArray = new int[rowsAffected.size()]; 962 for (int i = 0; i < rowsAffectedArray.length; i++) { 963 rowsAffectedArray[i] = rowsAffected.get(i); 964 } 965 return rowsAffectedArray; 966 } 967 } 968 finally { 969 if (pss instanceof ParameterDisposer) { 970 ((ParameterDisposer) pss).cleanupParameters(); 971 } 972 } 973 }); 974 975 Assert.state(result != null, "No result array"); 976 return result; 977 } 978 979 @Override 980 public int[] batchUpdate(String sql, List<Object[]> batchArgs) throws DataAccessException { 981 return batchUpdate(sql, batchArgs, new int[0]); 982 } 983 984 @Override 985 public int[] batchUpdate(String sql, List<Object[]> batchArgs, final int[] argTypes) throws DataAccessException { 986 if (batchArgs.isEmpty()) { 987 return new int[0]; 988 } 989 990 return batchUpdate( 991 sql, 992 new BatchPreparedStatementSetter() { 993 @Override 994 public void setValues(PreparedStatement ps, int i) throws SQLException { 995 Object[] values = batchArgs.get(i); 996 int colIndex = 0; 997 for (Object value : values) { 998 colIndex++; 999 if (value instanceof SqlParameterValue) { 1000 SqlParameterValue paramValue = (SqlParameterValue) value; 1001 StatementCreatorUtils.setParameterValue(ps, colIndex, paramValue, paramValue.getValue()); 1002 } 1003 else { 1004 int colType; 1005 if (argTypes.length < colIndex) { 1006 colType = SqlTypeValue.TYPE_UNKNOWN; 1007 } 1008 else { 1009 colType = argTypes[colIndex - 1]; 1010 } 1011 StatementCreatorUtils.setParameterValue(ps, colIndex, colType, value); 1012 } 1013 } 1014 } 1015 @Override 1016 public int getBatchSize() { 1017 return batchArgs.size(); 1018 } 1019 }); 1020 } 1021 1022 @Override 1023 public <T> int[][] batchUpdate(String sql, final Collection<T> batchArgs, final int batchSize, 1024 final ParameterizedPreparedStatementSetter<T> pss) throws DataAccessException { 1025 1026 if (logger.isDebugEnabled()) { 1027 logger.debug("Executing SQL batch update [" + sql + "] with a batch size of " + batchSize); 1028 } 1029 int[][] result = execute(sql, (PreparedStatementCallback<int[][]>) ps -> { 1030 List<int[]> rowsAffected = new ArrayList<>(); 1031 try { 1032 boolean batchSupported = JdbcUtils.supportsBatchUpdates(ps.getConnection()); 1033 int n = 0; 1034 for (T obj : batchArgs) { 1035 pss.setValues(ps, obj); 1036 n++; 1037 if (batchSupported) { 1038 ps.addBatch(); 1039 if (n % batchSize == 0 || n == batchArgs.size()) { 1040 if (logger.isTraceEnabled()) { 1041 int batchIdx = (n % batchSize == 0) ? n / batchSize : (n / batchSize) + 1; 1042 int items = n - ((n % batchSize == 0) ? n / batchSize - 1 : (n / batchSize)) * batchSize; 1043 logger.trace("Sending SQL batch update #" + batchIdx + " with " + items + " items"); 1044 } 1045 rowsAffected.add(ps.executeBatch()); 1046 } 1047 } 1048 else { 1049 int i = ps.executeUpdate(); 1050 rowsAffected.add(new int[] {i}); 1051 } 1052 } 1053 int[][] result1 = new int[rowsAffected.size()][]; 1054 for (int i = 0; i < result1.length; i++) { 1055 result1[i] = rowsAffected.get(i); 1056 } 1057 return result1; 1058 } 1059 finally { 1060 if (pss instanceof ParameterDisposer) { 1061 ((ParameterDisposer) pss).cleanupParameters(); 1062 } 1063 } 1064 }); 1065 1066 Assert.state(result != null, "No result array"); 1067 return result; 1068 } 1069 1070 1071 //------------------------------------------------------------------------- 1072 // Methods dealing with callable statements 1073 //------------------------------------------------------------------------- 1074 1075 @Override 1076 @Nullable 1077 public <T> T execute(CallableStatementCreator csc, CallableStatementCallback<T> action) 1078 throws DataAccessException { 1079 1080 Assert.notNull(csc, "CallableStatementCreator must not be null"); 1081 Assert.notNull(action, "Callback object must not be null"); 1082 if (logger.isDebugEnabled()) { 1083 String sql = getSql(csc); 1084 logger.debug("Calling stored procedure" + (sql != null ? " [" + sql + "]" : "")); 1085 } 1086 1087 Connection con = DataSourceUtils.getConnection(obtainDataSource()); 1088 CallableStatement cs = null; 1089 try { 1090 cs = csc.createCallableStatement(con); 1091 applyStatementSettings(cs); 1092 T result = action.doInCallableStatement(cs); 1093 handleWarnings(cs); 1094 return result; 1095 } 1096 catch (SQLException ex) { 1097 // Release Connection early, to avoid potential connection pool deadlock 1098 // in the case when the exception translator hasn't been initialized yet. 1099 if (csc instanceof ParameterDisposer) { 1100 ((ParameterDisposer) csc).cleanupParameters(); 1101 } 1102 String sql = getSql(csc); 1103 csc = null; 1104 JdbcUtils.closeStatement(cs); 1105 cs = null; 1106 DataSourceUtils.releaseConnection(con, getDataSource()); 1107 con = null; 1108 throw translateException("CallableStatementCallback", sql, ex); 1109 } 1110 finally { 1111 if (csc instanceof ParameterDisposer) { 1112 ((ParameterDisposer) csc).cleanupParameters(); 1113 } 1114 JdbcUtils.closeStatement(cs); 1115 DataSourceUtils.releaseConnection(con, getDataSource()); 1116 } 1117 } 1118 1119 @Override 1120 @Nullable 1121 public <T> T execute(String callString, CallableStatementCallback<T> action) throws DataAccessException { 1122 return execute(new SimpleCallableStatementCreator(callString), action); 1123 } 1124 1125 @Override 1126 public Map<String, Object> call(CallableStatementCreator csc, List<SqlParameter> declaredParameters) 1127 throws DataAccessException { 1128 1129 final List<SqlParameter> updateCountParameters = new ArrayList<>(); 1130 final List<SqlParameter> resultSetParameters = new ArrayList<>(); 1131 final List<SqlParameter> callParameters = new ArrayList<>(); 1132 1133 for (SqlParameter parameter : declaredParameters) { 1134 if (parameter.isResultsParameter()) { 1135 if (parameter instanceof SqlReturnResultSet) { 1136 resultSetParameters.add(parameter); 1137 } 1138 else { 1139 updateCountParameters.add(parameter); 1140 } 1141 } 1142 else { 1143 callParameters.add(parameter); 1144 } 1145 } 1146 1147 Map<String, Object> result = execute(csc, cs -> { 1148 boolean retVal = cs.execute(); 1149 int updateCount = cs.getUpdateCount(); 1150 if (logger.isTraceEnabled()) { 1151 logger.trace("CallableStatement.execute() returned '" + retVal + "'"); 1152 logger.trace("CallableStatement.getUpdateCount() returned " + updateCount); 1153 } 1154 Map<String, Object> resultsMap = createResultsMap(); 1155 if (retVal || updateCount != -1) { 1156 resultsMap.putAll(extractReturnedResults(cs, updateCountParameters, resultSetParameters, updateCount)); 1157 } 1158 resultsMap.putAll(extractOutputParameters(cs, callParameters)); 1159 return resultsMap; 1160 }); 1161 1162 Assert.state(result != null, "No result map"); 1163 return result; 1164 } 1165 1166 /** 1167 * Extract returned ResultSets from the completed stored procedure. 1168 * @param cs a JDBC wrapper for the stored procedure 1169 * @param updateCountParameters the parameter list of declared update count parameters for the stored procedure 1170 * @param resultSetParameters the parameter list of declared resultSet parameters for the stored procedure 1171 * @return a Map that contains returned results 1172 */ 1173 protected Map<String, Object> extractReturnedResults(CallableStatement cs, 1174 @Nullable List<SqlParameter> updateCountParameters, @Nullable List<SqlParameter> resultSetParameters, 1175 int updateCount) throws SQLException { 1176 1177 Map<String, Object> results = new LinkedHashMap<>(4); 1178 int rsIndex = 0; 1179 int updateIndex = 0; 1180 boolean moreResults; 1181 if (!this.skipResultsProcessing) { 1182 do { 1183 if (updateCount == -1) { 1184 if (resultSetParameters != null && resultSetParameters.size() > rsIndex) { 1185 SqlReturnResultSet declaredRsParam = (SqlReturnResultSet) resultSetParameters.get(rsIndex); 1186 results.putAll(processResultSet(cs.getResultSet(), declaredRsParam)); 1187 rsIndex++; 1188 } 1189 else { 1190 if (!this.skipUndeclaredResults) { 1191 String rsName = RETURN_RESULT_SET_PREFIX + (rsIndex + 1); 1192 SqlReturnResultSet undeclaredRsParam = new SqlReturnResultSet(rsName, getColumnMapRowMapper()); 1193 if (logger.isTraceEnabled()) { 1194 logger.trace("Added default SqlReturnResultSet parameter named '" + rsName + "'"); 1195 } 1196 results.putAll(processResultSet(cs.getResultSet(), undeclaredRsParam)); 1197 rsIndex++; 1198 } 1199 } 1200 } 1201 else { 1202 if (updateCountParameters != null && updateCountParameters.size() > updateIndex) { 1203 SqlReturnUpdateCount ucParam = (SqlReturnUpdateCount) updateCountParameters.get(updateIndex); 1204 String declaredUcName = ucParam.getName(); 1205 results.put(declaredUcName, updateCount); 1206 updateIndex++; 1207 } 1208 else { 1209 if (!this.skipUndeclaredResults) { 1210 String undeclaredName = RETURN_UPDATE_COUNT_PREFIX + (updateIndex + 1); 1211 if (logger.isTraceEnabled()) { 1212 logger.trace("Added default SqlReturnUpdateCount parameter named '" + undeclaredName + "'"); 1213 } 1214 results.put(undeclaredName, updateCount); 1215 updateIndex++; 1216 } 1217 } 1218 } 1219 moreResults = cs.getMoreResults(); 1220 updateCount = cs.getUpdateCount(); 1221 if (logger.isTraceEnabled()) { 1222 logger.trace("CallableStatement.getUpdateCount() returned " + updateCount); 1223 } 1224 } 1225 while (moreResults || updateCount != -1); 1226 } 1227 return results; 1228 } 1229 1230 /** 1231 * Extract output parameters from the completed stored procedure. 1232 * @param cs the JDBC wrapper for the stored procedure 1233 * @param parameters parameter list for the stored procedure 1234 * @return a Map that contains returned results 1235 */ 1236 protected Map<String, Object> extractOutputParameters(CallableStatement cs, List<SqlParameter> parameters) 1237 throws SQLException { 1238 1239 Map<String, Object> results = new LinkedHashMap<>(parameters.size()); 1240 int sqlColIndex = 1; 1241 for (SqlParameter param : parameters) { 1242 if (param instanceof SqlOutParameter) { 1243 SqlOutParameter outParam = (SqlOutParameter) param; 1244 Assert.state(outParam.getName() != null, "Anonymous parameters not allowed"); 1245 SqlReturnType returnType = outParam.getSqlReturnType(); 1246 if (returnType != null) { 1247 Object out = returnType.getTypeValue(cs, sqlColIndex, outParam.getSqlType(), outParam.getTypeName()); 1248 results.put(outParam.getName(), out); 1249 } 1250 else { 1251 Object out = cs.getObject(sqlColIndex); 1252 if (out instanceof ResultSet) { 1253 if (outParam.isResultSetSupported()) { 1254 results.putAll(processResultSet((ResultSet) out, outParam)); 1255 } 1256 else { 1257 String rsName = outParam.getName(); 1258 SqlReturnResultSet rsParam = new SqlReturnResultSet(rsName, getColumnMapRowMapper()); 1259 results.putAll(processResultSet((ResultSet) out, rsParam)); 1260 if (logger.isTraceEnabled()) { 1261 logger.trace("Added default SqlReturnResultSet parameter named '" + rsName + "'"); 1262 } 1263 } 1264 } 1265 else { 1266 results.put(outParam.getName(), out); 1267 } 1268 } 1269 } 1270 if (!(param.isResultsParameter())) { 1271 sqlColIndex++; 1272 } 1273 } 1274 return results; 1275 } 1276 1277 /** 1278 * Process the given ResultSet from a stored procedure. 1279 * @param rs the ResultSet to process 1280 * @param param the corresponding stored procedure parameter 1281 * @return a Map that contains returned results 1282 */ 1283 protected Map<String, Object> processResultSet( 1284 @Nullable ResultSet rs, ResultSetSupportingSqlParameter param) throws SQLException { 1285 1286 if (rs != null) { 1287 try { 1288 if (param.getRowMapper() != null) { 1289 RowMapper<?> rowMapper = param.getRowMapper(); 1290 Object data = (new RowMapperResultSetExtractor<>(rowMapper)).extractData(rs); 1291 return Collections.singletonMap(param.getName(), data); 1292 } 1293 else if (param.getRowCallbackHandler() != null) { 1294 RowCallbackHandler rch = param.getRowCallbackHandler(); 1295 (new RowCallbackHandlerResultSetExtractor(rch)).extractData(rs); 1296 return Collections.singletonMap(param.getName(), 1297 "ResultSet returned from stored procedure was processed"); 1298 } 1299 else if (param.getResultSetExtractor() != null) { 1300 Object data = param.getResultSetExtractor().extractData(rs); 1301 return Collections.singletonMap(param.getName(), data); 1302 } 1303 } 1304 finally { 1305 JdbcUtils.closeResultSet(rs); 1306 } 1307 } 1308 return Collections.emptyMap(); 1309 } 1310 1311 1312 //------------------------------------------------------------------------- 1313 // Implementation hooks and helper methods 1314 //------------------------------------------------------------------------- 1315 1316 /** 1317 * Create a new RowMapper for reading columns as key-value pairs. 1318 * @return the RowMapper to use 1319 * @see ColumnMapRowMapper 1320 */ 1321 protected RowMapper<Map<String, Object>> getColumnMapRowMapper() { 1322 return new ColumnMapRowMapper(); 1323 } 1324 1325 /** 1326 * Create a new RowMapper for reading result objects from a single column. 1327 * @param requiredType the type that each result object is expected to match 1328 * @return the RowMapper to use 1329 * @see SingleColumnRowMapper 1330 */ 1331 protected <T> RowMapper<T> getSingleColumnRowMapper(Class<T> requiredType) { 1332 return new SingleColumnRowMapper<>(requiredType); 1333 } 1334 1335 /** 1336 * Create a Map instance to be used as the results map. 1337 * <p>If {@link #resultsMapCaseInsensitive} has been set to true, 1338 * a {@link LinkedCaseInsensitiveMap} will be created; otherwise, a 1339 * {@link LinkedHashMap} will be created. 1340 * @return the results Map instance 1341 * @see #setResultsMapCaseInsensitive 1342 * @see #isResultsMapCaseInsensitive 1343 */ 1344 protected Map<String, Object> createResultsMap() { 1345 if (isResultsMapCaseInsensitive()) { 1346 return new LinkedCaseInsensitiveMap<>(); 1347 } 1348 else { 1349 return new LinkedHashMap<>(); 1350 } 1351 } 1352 1353 /** 1354 * Prepare the given JDBC Statement (or PreparedStatement or CallableStatement), 1355 * applying statement settings such as fetch size, max rows, and query timeout. 1356 * @param stmt the JDBC Statement to prepare 1357 * @throws SQLException if thrown by JDBC API 1358 * @see #setFetchSize 1359 * @see #setMaxRows 1360 * @see #setQueryTimeout 1361 * @see org.springframework.jdbc.datasource.DataSourceUtils#applyTransactionTimeout 1362 */ 1363 protected void applyStatementSettings(Statement stmt) throws SQLException { 1364 int fetchSize = getFetchSize(); 1365 if (fetchSize != -1) { 1366 stmt.setFetchSize(fetchSize); 1367 } 1368 int maxRows = getMaxRows(); 1369 if (maxRows != -1) { 1370 stmt.setMaxRows(maxRows); 1371 } 1372 DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout()); 1373 } 1374 1375 /** 1376 * Create a new arg-based PreparedStatementSetter using the args passed in. 1377 * <p>By default, we'll create an {@link ArgumentPreparedStatementSetter}. 1378 * This method allows for the creation to be overridden by subclasses. 1379 * @param args object array with arguments 1380 * @return the new PreparedStatementSetter to use 1381 */ 1382 protected PreparedStatementSetter newArgPreparedStatementSetter(@Nullable Object[] args) { 1383 return new ArgumentPreparedStatementSetter(args); 1384 } 1385 1386 /** 1387 * Create a new arg-type-based PreparedStatementSetter using the args and types passed in. 1388 * <p>By default, we'll create an {@link ArgumentTypePreparedStatementSetter}. 1389 * This method allows for the creation to be overridden by subclasses. 1390 * @param args object array with arguments 1391 * @param argTypes int array of SQLTypes for the associated arguments 1392 * @return the new PreparedStatementSetter to use 1393 */ 1394 protected PreparedStatementSetter newArgTypePreparedStatementSetter(Object[] args, int[] argTypes) { 1395 return new ArgumentTypePreparedStatementSetter(args, argTypes); 1396 } 1397 1398 /** 1399 * Throw an SQLWarningException if we're not ignoring warnings, 1400 * otherwise log the warnings at debug level. 1401 * @param stmt the current JDBC statement 1402 * @throws SQLWarningException if not ignoring warnings 1403 * @see org.springframework.jdbc.SQLWarningException 1404 */ 1405 protected void handleWarnings(Statement stmt) throws SQLException { 1406 if (isIgnoreWarnings()) { 1407 if (logger.isDebugEnabled()) { 1408 SQLWarning warningToLog = stmt.getWarnings(); 1409 while (warningToLog != null) { 1410 logger.debug("SQLWarning ignored: SQL state '" + warningToLog.getSQLState() + "', error code '" + 1411 warningToLog.getErrorCode() + "', message [" + warningToLog.getMessage() + "]"); 1412 warningToLog = warningToLog.getNextWarning(); 1413 } 1414 } 1415 } 1416 else { 1417 handleWarnings(stmt.getWarnings()); 1418 } 1419 } 1420 1421 /** 1422 * Throw an SQLWarningException if encountering an actual warning. 1423 * @param warning the warnings object from the current statement. 1424 * May be {@code null}, in which case this method does nothing. 1425 * @throws SQLWarningException in case of an actual warning to be raised 1426 */ 1427 protected void handleWarnings(@Nullable SQLWarning warning) throws SQLWarningException { 1428 if (warning != null) { 1429 throw new SQLWarningException("Warning not ignored", warning); 1430 } 1431 } 1432 1433 /** 1434 * Translate the given {@link SQLException} into a generic {@link DataAccessException}. 1435 * @param task readable text describing the task being attempted 1436 * @param sql the SQL query or update that caused the problem (may be {@code null}) 1437 * @param ex the offending {@code SQLException} 1438 * @return a DataAccessException wrapping the {@code SQLException} (never {@code null}) 1439 * @since 5.0 1440 * @see #getExceptionTranslator() 1441 */ 1442 protected DataAccessException translateException(String task, @Nullable String sql, SQLException ex) { 1443 DataAccessException dae = getExceptionTranslator().translate(task, sql, ex); 1444 return (dae != null ? dae : new UncategorizedSQLException(task, sql, ex)); 1445 } 1446 1447 1448 /** 1449 * Determine SQL from potential provider object. 1450 * @param sqlProvider object which is potentially an SqlProvider 1451 * @return the SQL string, or {@code null} if not known 1452 * @see SqlProvider 1453 */ 1454 @Nullable 1455 private static String getSql(Object sqlProvider) { 1456 if (sqlProvider instanceof SqlProvider) { 1457 return ((SqlProvider) sqlProvider).getSql(); 1458 } 1459 else { 1460 return null; 1461 } 1462 } 1463 1464 private static <T> T result(@Nullable T result) { 1465 Assert.state(result != null, "No result"); 1466 return result; 1467 } 1468 1469 private static int updateCount(@Nullable Integer result) { 1470 Assert.state(result != null, "No update count"); 1471 return result; 1472 } 1473 1474 1475 /** 1476 * Invocation handler that suppresses close calls on JDBC Connections. 1477 * Also prepares returned Statement (Prepared/CallbackStatement) objects. 1478 * @see java.sql.Connection#close() 1479 */ 1480 private class CloseSuppressingInvocationHandler implements InvocationHandler { 1481 1482 private final Connection target; 1483 1484 public CloseSuppressingInvocationHandler(Connection target) { 1485 this.target = target; 1486 } 1487 1488 @Override 1489 @Nullable 1490 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 1491 // Invocation on ConnectionProxy interface coming in... 1492 1493 if (method.getName().equals("equals")) { 1494 // Only consider equal when proxies are identical. 1495 return (proxy == args[0]); 1496 } 1497 else if (method.getName().equals("hashCode")) { 1498 // Use hashCode of PersistenceManager proxy. 1499 return System.identityHashCode(proxy); 1500 } 1501 else if (method.getName().equals("unwrap")) { 1502 if (((Class<?>) args[0]).isInstance(proxy)) { 1503 return proxy; 1504 } 1505 } 1506 else if (method.getName().equals("isWrapperFor")) { 1507 if (((Class<?>) args[0]).isInstance(proxy)) { 1508 return true; 1509 } 1510 } 1511 else if (method.getName().equals("close")) { 1512 // Handle close method: suppress, not valid. 1513 return null; 1514 } 1515 else if (method.getName().equals("isClosed")) { 1516 return false; 1517 } 1518 else if (method.getName().equals("getTargetConnection")) { 1519 // Handle getTargetConnection method: return underlying Connection. 1520 return this.target; 1521 } 1522 1523 // Invoke method on target Connection. 1524 try { 1525 Object retVal = method.invoke(this.target, args); 1526 1527 // If return value is a JDBC Statement, apply statement settings 1528 // (fetch size, max rows, transaction timeout). 1529 if (retVal instanceof Statement) { 1530 applyStatementSettings(((Statement) retVal)); 1531 } 1532 1533 return retVal; 1534 } 1535 catch (InvocationTargetException ex) { 1536 throw ex.getTargetException(); 1537 } 1538 } 1539 } 1540 1541 1542 /** 1543 * Simple adapter for PreparedStatementCreator, allowing to use a plain SQL statement. 1544 */ 1545 private static class SimplePreparedStatementCreator implements PreparedStatementCreator, SqlProvider { 1546 1547 private final String sql; 1548 1549 public SimplePreparedStatementCreator(String sql) { 1550 Assert.notNull(sql, "SQL must not be null"); 1551 this.sql = sql; 1552 } 1553 1554 @Override 1555 public PreparedStatement createPreparedStatement(Connection con) throws SQLException { 1556 return con.prepareStatement(this.sql); 1557 } 1558 1559 @Override 1560 public String getSql() { 1561 return this.sql; 1562 } 1563 } 1564 1565 1566 /** 1567 * Simple adapter for CallableStatementCreator, allowing to use a plain SQL statement. 1568 */ 1569 private static class SimpleCallableStatementCreator implements CallableStatementCreator, SqlProvider { 1570 1571 private final String callString; 1572 1573 public SimpleCallableStatementCreator(String callString) { 1574 Assert.notNull(callString, "Call string must not be null"); 1575 this.callString = callString; 1576 } 1577 1578 @Override 1579 public CallableStatement createCallableStatement(Connection con) throws SQLException { 1580 return con.prepareCall(this.callString); 1581 } 1582 1583 @Override 1584 public String getSql() { 1585 return this.callString; 1586 } 1587 } 1588 1589 1590 /** 1591 * Adapter to enable use of a RowCallbackHandler inside a ResultSetExtractor. 1592 * <p>Uses a regular ResultSet, so we have to be careful when using it: 1593 * We don't use it for navigating since this could lead to unpredictable consequences. 1594 */ 1595 private static class RowCallbackHandlerResultSetExtractor implements ResultSetExtractor<Object> { 1596 1597 private final RowCallbackHandler rch; 1598 1599 public RowCallbackHandlerResultSetExtractor(RowCallbackHandler rch) { 1600 this.rch = rch; 1601 } 1602 1603 @Override 1604 @Nullable 1605 public Object extractData(ResultSet rs) throws SQLException { 1606 while (rs.next()) { 1607 this.rch.processRow(rs); 1608 } 1609 return null; 1610 } 1611 } 1612 1613}