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