001/* 002 * Copyright 2002-2019 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.object; 018 019import java.sql.ResultSet; 020import java.sql.Types; 021import java.util.Arrays; 022import java.util.Collections; 023import java.util.LinkedList; 024import java.util.List; 025import java.util.Map; 026 027import javax.sql.DataSource; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031 032import org.springframework.beans.factory.InitializingBean; 033import org.springframework.dao.InvalidDataAccessApiUsageException; 034import org.springframework.jdbc.core.JdbcTemplate; 035import org.springframework.jdbc.core.SqlParameter; 036import org.springframework.lang.Nullable; 037import org.springframework.util.Assert; 038 039/** 040 * An "RDBMS operation" is a multi-threaded, reusable object representing a query, 041 * update, or stored procedure call. An RDBMS operation is <b>not</b> a command, 042 * as a command is not reusable. However, execute methods may take commands as 043 * arguments. Subclasses should be JavaBeans, allowing easy configuration. 044 * 045 * <p>This class and subclasses throw runtime exceptions, defined in the 046 * {@code org.springframework.dao} package (and as thrown by the 047 * {@code org.springframework.jdbc.core} package, which the classes 048 * in this package use under the hood to perform raw JDBC operations). 049 * 050 * <p>Subclasses should set SQL and add parameters before invoking the 051 * {@link #compile()} method. The order in which parameters are added is 052 * significant. The appropriate {@code execute} or {@code update} 053 * method can then be invoked. 054 * 055 * @author Rod Johnson 056 * @author Juergen Hoeller 057 * @see SqlQuery 058 * @see SqlUpdate 059 * @see StoredProcedure 060 * @see org.springframework.jdbc.core.JdbcTemplate 061 */ 062public abstract class RdbmsOperation implements InitializingBean { 063 064 /** Logger available to subclasses. */ 065 protected final Log logger = LogFactory.getLog(getClass()); 066 067 /** Lower-level class used to execute SQL. */ 068 private JdbcTemplate jdbcTemplate = new JdbcTemplate(); 069 070 private int resultSetType = ResultSet.TYPE_FORWARD_ONLY; 071 072 private boolean updatableResults = false; 073 074 private boolean returnGeneratedKeys = false; 075 076 @Nullable 077 private String[] generatedKeysColumnNames; 078 079 @Nullable 080 private String sql; 081 082 private final List<SqlParameter> declaredParameters = new LinkedList<>(); 083 084 /** 085 * Has this operation been compiled? Compilation means at 086 * least checking that a DataSource and sql have been provided, 087 * but subclasses may also implement their own custom validation. 088 */ 089 private volatile boolean compiled; 090 091 092 /** 093 * An alternative to the more commonly used {@link #setDataSource} when you want to 094 * use the same {@link JdbcTemplate} in multiple {@code RdbmsOperations}. This is 095 * appropriate if the {@code JdbcTemplate} has special configuration such as a 096 * {@link org.springframework.jdbc.support.SQLExceptionTranslator} to be reused. 097 */ 098 public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { 099 this.jdbcTemplate = jdbcTemplate; 100 } 101 102 /** 103 * Return the {@link JdbcTemplate} used by this operation object. 104 */ 105 public JdbcTemplate getJdbcTemplate() { 106 return this.jdbcTemplate; 107 } 108 109 /** 110 * Set the JDBC {@link DataSource} to obtain connections from. 111 * @see org.springframework.jdbc.core.JdbcTemplate#setDataSource 112 */ 113 public void setDataSource(DataSource dataSource) { 114 this.jdbcTemplate.setDataSource(dataSource); 115 } 116 117 /** 118 * Set the fetch size for this RDBMS operation. This is important for processing 119 * large result sets: Setting this higher than the default value will increase 120 * processing speed at the cost of memory consumption; setting this lower can 121 * avoid transferring row data that will never be read by the application. 122 * <p>Default is -1, indicating to use the driver's default. 123 * @see org.springframework.jdbc.core.JdbcTemplate#setFetchSize 124 */ 125 public void setFetchSize(int fetchSize) { 126 this.jdbcTemplate.setFetchSize(fetchSize); 127 } 128 129 /** 130 * Set the maximum number of rows for this RDBMS operation. This is important 131 * for processing subsets of large result sets, avoiding to read and hold 132 * the entire result set in the database or in the JDBC driver. 133 * <p>Default is -1, indicating to use the driver's default. 134 * @see org.springframework.jdbc.core.JdbcTemplate#setMaxRows 135 */ 136 public void setMaxRows(int maxRows) { 137 this.jdbcTemplate.setMaxRows(maxRows); 138 } 139 140 /** 141 * Set the query timeout for statements that this RDBMS operation executes. 142 * <p>Default is -1, indicating to use the JDBC driver's default. 143 * <p>Note: Any timeout specified here will be overridden by the remaining 144 * transaction timeout when executing within a transaction that has a 145 * timeout specified at the transaction level. 146 */ 147 public void setQueryTimeout(int queryTimeout) { 148 this.jdbcTemplate.setQueryTimeout(queryTimeout); 149 } 150 151 /** 152 * Set whether to use statements that return a specific type of ResultSet. 153 * @param resultSetType the ResultSet type 154 * @see java.sql.ResultSet#TYPE_FORWARD_ONLY 155 * @see java.sql.ResultSet#TYPE_SCROLL_INSENSITIVE 156 * @see java.sql.ResultSet#TYPE_SCROLL_SENSITIVE 157 * @see java.sql.Connection#prepareStatement(String, int, int) 158 */ 159 public void setResultSetType(int resultSetType) { 160 this.resultSetType = resultSetType; 161 } 162 163 /** 164 * Return whether statements will return a specific type of ResultSet. 165 */ 166 public int getResultSetType() { 167 return this.resultSetType; 168 } 169 170 /** 171 * Set whether to use statements that are capable of returning 172 * updatable ResultSets. 173 * @see java.sql.Connection#prepareStatement(String, int, int) 174 */ 175 public void setUpdatableResults(boolean updatableResults) { 176 if (isCompiled()) { 177 throw new InvalidDataAccessApiUsageException( 178 "The updateableResults flag must be set before the operation is compiled"); 179 } 180 this.updatableResults = updatableResults; 181 } 182 183 /** 184 * Return whether statements will return updatable ResultSets. 185 */ 186 public boolean isUpdatableResults() { 187 return this.updatableResults; 188 } 189 190 /** 191 * Set whether prepared statements should be capable of returning 192 * auto-generated keys. 193 * @see java.sql.Connection#prepareStatement(String, int) 194 */ 195 public void setReturnGeneratedKeys(boolean returnGeneratedKeys) { 196 if (isCompiled()) { 197 throw new InvalidDataAccessApiUsageException( 198 "The returnGeneratedKeys flag must be set before the operation is compiled"); 199 } 200 this.returnGeneratedKeys = returnGeneratedKeys; 201 } 202 203 /** 204 * Return whether statements should be capable of returning 205 * auto-generated keys. 206 */ 207 public boolean isReturnGeneratedKeys() { 208 return this.returnGeneratedKeys; 209 } 210 211 /** 212 * Set the column names of the auto-generated keys. 213 * @see java.sql.Connection#prepareStatement(String, String[]) 214 */ 215 public void setGeneratedKeysColumnNames(@Nullable String... names) { 216 if (isCompiled()) { 217 throw new InvalidDataAccessApiUsageException( 218 "The column names for the generated keys must be set before the operation is compiled"); 219 } 220 this.generatedKeysColumnNames = names; 221 } 222 223 /** 224 * Return the column names of the auto generated keys. 225 */ 226 @Nullable 227 public String[] getGeneratedKeysColumnNames() { 228 return this.generatedKeysColumnNames; 229 } 230 231 /** 232 * Set the SQL executed by this operation. 233 */ 234 public void setSql(@Nullable String sql) { 235 this.sql = sql; 236 } 237 238 /** 239 * Subclasses can override this to supply dynamic SQL if they wish, but SQL is 240 * normally set by calling the {@link #setSql} method or in a subclass constructor. 241 */ 242 @Nullable 243 public String getSql() { 244 return this.sql; 245 } 246 247 /** 248 * Resolve the configured SQL for actual use. 249 * @return the SQL (never {@code null}) 250 * @since 5.0 251 */ 252 protected String resolveSql() { 253 String sql = getSql(); 254 Assert.state(sql != null, "No SQL set"); 255 return sql; 256 } 257 258 /** 259 * Add anonymous parameters, specifying only their SQL types 260 * as defined in the {@code java.sql.Types} class. 261 * <p>Parameter ordering is significant. This method is an alternative 262 * to the {@link #declareParameter} method, which should normally be preferred. 263 * @param types array of SQL types as defined in the 264 * {@code java.sql.Types} class 265 * @throws InvalidDataAccessApiUsageException if the operation is already compiled 266 */ 267 public void setTypes(@Nullable int[] types) throws InvalidDataAccessApiUsageException { 268 if (isCompiled()) { 269 throw new InvalidDataAccessApiUsageException("Cannot add parameters once query is compiled"); 270 } 271 if (types != null) { 272 for (int type : types) { 273 declareParameter(new SqlParameter(type)); 274 } 275 } 276 } 277 278 /** 279 * Declare a parameter for this operation. 280 * <p>The order in which this method is called is significant when using 281 * positional parameters. It is not significant when using named parameters 282 * with named SqlParameter objects here; it remains significant when using 283 * named parameters in combination with unnamed SqlParameter objects here. 284 * @param param the SqlParameter to add. This will specify SQL type and (optionally) 285 * the parameter's name. Note that you typically use the {@link SqlParameter} class 286 * itself here, not any of its subclasses. 287 * @throws InvalidDataAccessApiUsageException if the operation is already compiled, 288 * and hence cannot be configured further 289 */ 290 public void declareParameter(SqlParameter param) throws InvalidDataAccessApiUsageException { 291 if (isCompiled()) { 292 throw new InvalidDataAccessApiUsageException("Cannot add parameters once the query is compiled"); 293 } 294 this.declaredParameters.add(param); 295 } 296 297 /** 298 * Add one or more declared parameters. Used for configuring this operation 299 * when used in a bean factory. Each parameter will specify SQL type and (optionally) 300 * the parameter's name. 301 * @param parameters an array containing the declared {@link SqlParameter} objects 302 * @see #declaredParameters 303 */ 304 public void setParameters(SqlParameter... parameters) { 305 if (isCompiled()) { 306 throw new InvalidDataAccessApiUsageException("Cannot add parameters once the query is compiled"); 307 } 308 for (int i = 0; i < parameters.length; i++) { 309 if (parameters[i] != null) { 310 this.declaredParameters.add(parameters[i]); 311 } 312 else { 313 throw new InvalidDataAccessApiUsageException("Cannot add parameter at index " + i + " from " + 314 Arrays.asList(parameters) + " since it is 'null'"); 315 } 316 } 317 } 318 319 /** 320 * Return a list of the declared {@link SqlParameter} objects. 321 */ 322 protected List<SqlParameter> getDeclaredParameters() { 323 return this.declaredParameters; 324 } 325 326 327 /** 328 * Ensures compilation if used in a bean factory. 329 */ 330 @Override 331 public void afterPropertiesSet() { 332 compile(); 333 } 334 335 /** 336 * Compile this query. 337 * Ignores subsequent attempts to compile. 338 * @throws InvalidDataAccessApiUsageException if the object hasn't 339 * been correctly initialized, for example if no DataSource has been provided 340 */ 341 public final void compile() throws InvalidDataAccessApiUsageException { 342 if (!isCompiled()) { 343 if (getSql() == null) { 344 throw new InvalidDataAccessApiUsageException("Property 'sql' is required"); 345 } 346 347 try { 348 this.jdbcTemplate.afterPropertiesSet(); 349 } 350 catch (IllegalArgumentException ex) { 351 throw new InvalidDataAccessApiUsageException(ex.getMessage()); 352 } 353 354 compileInternal(); 355 this.compiled = true; 356 357 if (logger.isDebugEnabled()) { 358 logger.debug("RdbmsOperation with SQL [" + getSql() + "] compiled"); 359 } 360 } 361 } 362 363 /** 364 * Is this operation "compiled"? Compilation, as in JDO, 365 * means that the operation is fully configured, and ready to use. 366 * The exact meaning of compilation will vary between subclasses. 367 * @return whether this operation is compiled and ready to use 368 */ 369 public boolean isCompiled() { 370 return this.compiled; 371 } 372 373 /** 374 * Check whether this operation has been compiled already; 375 * lazily compile it if not already compiled. 376 * <p>Automatically called by {@code validateParameters}. 377 * @see #validateParameters 378 */ 379 protected void checkCompiled() { 380 if (!isCompiled()) { 381 logger.debug("SQL operation not compiled before execution - invoking compile"); 382 compile(); 383 } 384 } 385 386 /** 387 * Validate the parameters passed to an execute method based on declared parameters. 388 * Subclasses should invoke this method before every {@code executeQuery()} 389 * or {@code update()} method. 390 * @param parameters the parameters supplied (may be {@code null}) 391 * @throws InvalidDataAccessApiUsageException if the parameters are invalid 392 */ 393 protected void validateParameters(@Nullable Object[] parameters) throws InvalidDataAccessApiUsageException { 394 checkCompiled(); 395 int declaredInParameters = 0; 396 for (SqlParameter param : this.declaredParameters) { 397 if (param.isInputValueProvided()) { 398 if (!supportsLobParameters() && 399 (param.getSqlType() == Types.BLOB || param.getSqlType() == Types.CLOB)) { 400 throw new InvalidDataAccessApiUsageException( 401 "BLOB or CLOB parameters are not allowed for this kind of operation"); 402 } 403 declaredInParameters++; 404 } 405 } 406 validateParameterCount((parameters != null ? parameters.length : 0), declaredInParameters); 407 } 408 409 /** 410 * Validate the named parameters passed to an execute method based on declared parameters. 411 * Subclasses should invoke this method before every {@code executeQuery()} or 412 * {@code update()} method. 413 * @param parameters parameter Map supplied (may be {@code null}) 414 * @throws InvalidDataAccessApiUsageException if the parameters are invalid 415 */ 416 protected void validateNamedParameters(@Nullable Map<String, ?> parameters) throws InvalidDataAccessApiUsageException { 417 checkCompiled(); 418 Map<String, ?> paramsToUse = (parameters != null ? parameters : Collections.<String, Object> emptyMap()); 419 int declaredInParameters = 0; 420 for (SqlParameter param : this.declaredParameters) { 421 if (param.isInputValueProvided()) { 422 if (!supportsLobParameters() && 423 (param.getSqlType() == Types.BLOB || param.getSqlType() == Types.CLOB)) { 424 throw new InvalidDataAccessApiUsageException( 425 "BLOB or CLOB parameters are not allowed for this kind of operation"); 426 } 427 if (param.getName() != null && !paramsToUse.containsKey(param.getName())) { 428 throw new InvalidDataAccessApiUsageException("The parameter named '" + param.getName() + 429 "' was not among the parameters supplied: " + paramsToUse.keySet()); 430 } 431 declaredInParameters++; 432 } 433 } 434 validateParameterCount(paramsToUse.size(), declaredInParameters); 435 } 436 437 /** 438 * Validate the given parameter count against the given declared parameters. 439 * @param suppliedParamCount the number of actual parameters given 440 * @param declaredInParamCount the number of input parameters declared 441 */ 442 private void validateParameterCount(int suppliedParamCount, int declaredInParamCount) { 443 if (suppliedParamCount < declaredInParamCount) { 444 throw new InvalidDataAccessApiUsageException(suppliedParamCount + " parameters were supplied, but " + 445 declaredInParamCount + " in parameters were declared in class [" + getClass().getName() + "]"); 446 } 447 if (suppliedParamCount > this.declaredParameters.size() && !allowsUnusedParameters()) { 448 throw new InvalidDataAccessApiUsageException(suppliedParamCount + " parameters were supplied, but " + 449 declaredInParamCount + " parameters were declared in class [" + getClass().getName() + "]"); 450 } 451 } 452 453 454 /** 455 * Subclasses must implement this template method to perform their own compilation. 456 * Invoked after this base class's compilation is complete. 457 * <p>Subclasses can assume that SQL and a DataSource have been supplied. 458 * @throws InvalidDataAccessApiUsageException if the subclass hasn't been 459 * properly configured 460 */ 461 protected abstract void compileInternal() throws InvalidDataAccessApiUsageException; 462 463 /** 464 * Return whether BLOB/CLOB parameters are supported for this kind of operation. 465 * <p>The default is {@code true}. 466 */ 467 protected boolean supportsLobParameters() { 468 return true; 469 } 470 471 /** 472 * Return whether this operation accepts additional parameters that are 473 * given but not actually used. Applies in particular to parameter Maps. 474 * <p>The default is {@code false}. 475 * @see StoredProcedure 476 */ 477 protected boolean allowsUnusedParameters() { 478 return false; 479 } 480 481}