001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.jdbc.object;
018:
019: import java.sql.ResultSet;
020: import java.sql.Types;
021: import java.util.*;
022:
023: import javax.sql.DataSource;
024:
025: import org.apache.commons.logging.Log;
026: import org.apache.commons.logging.LogFactory;
027:
028: import org.springframework.beans.factory.InitializingBean;
029: import org.springframework.dao.InvalidDataAccessApiUsageException;
030: import org.springframework.jdbc.core.JdbcTemplate;
031: import org.springframework.jdbc.core.SqlParameter;
032:
033: /**
034: * An "RDBMS operation" is a multi-threaded, reusable object representing a query,
035: * update, or stored procedure call. An RDBMS operation is <b>not</b> a command,
036: * as a command is not reusable. However, execute methods may take commands as
037: * arguments. Subclasses should be JavaBeans, allowing easy configuration.
038: *
039: * <p>This class and subclasses throw runtime exceptions, defined in the
040: * <codeorg.springframework.dao package</code> (and as thrown by the
041: * <code>org.springframework.jdbc.core</code> package, which the classes
042: * in this package use under the hood to perform raw JDBC operations).
043: *
044: * <p>Subclasses should set SQL and add parameters before invoking the
045: * {@link #compile()} method. The order in which parameters are added is
046: * significant. The appropriate <code>execute</code> or <code>update</code>
047: * method can then be invoked.
048: *
049: * @author Rod Johnson
050: * @author Juergen Hoeller
051: * @see SqlQuery
052: * @see SqlUpdate
053: * @see StoredProcedure
054: * @see org.springframework.jdbc.core.JdbcTemplate
055: */
056: public abstract class RdbmsOperation implements InitializingBean {
057:
058: /** Logger available to subclasses */
059: protected final Log logger = LogFactory.getLog(getClass());
060:
061: /** Lower-level class used to execute SQL */
062: private JdbcTemplate jdbcTemplate = new JdbcTemplate();
063:
064: private int resultSetType = ResultSet.TYPE_FORWARD_ONLY;
065:
066: private boolean updatableResults = false;
067:
068: private boolean returnGeneratedKeys = false;
069:
070: private String[] generatedKeysColumnNames = null;
071:
072: /** SQL statement */
073: private String sql;
074:
075: /** List of SqlParameter objects */
076: private final List declaredParameters = new LinkedList();
077:
078: /**
079: * Has this operation been compiled? Compilation means at
080: * least checking that a DataSource and sql have been provided,
081: * but subclasses may also implement their own custom validation.
082: */
083: private boolean compiled;
084:
085: /**
086: * An alternative to the more commonly used setDataSource() when you want to
087: * use the same JdbcTemplate in multiple RdbmsOperations. This is appropriate if the
088: * JdbcTemplate has special configuration such as a SQLExceptionTranslator that should
089: * apply to multiple RdbmsOperation objects.
090: */
091: public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
092: if (jdbcTemplate == null) {
093: throw new IllegalArgumentException(
094: "jdbcTemplate must not be null");
095: }
096: this .jdbcTemplate = jdbcTemplate;
097: }
098:
099: /**
100: * Return the JdbcTemplate object used by this object.
101: */
102: public JdbcTemplate getJdbcTemplate() {
103: return this .jdbcTemplate;
104: }
105:
106: /**
107: * Set the JDBC 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 0, 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 0, 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 0, 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,
236: * but SQL is normally set by calling the setSql() method
237: * or in a subclass constructor.
238: */
239: public String getSql() {
240: return this .sql;
241: }
242:
243: /**
244: * Add anonymous parameters, specifying only their SQL types
245: * as defined in the <code>java.sql.Types</code> class.
246: * <p>Parameter ordering is significant. This method is an alternative
247: * to the {@link #declareParameter} method, which should normally be preferred.
248: * @param types array of SQL types as defined in the
249: * <code>java.sql.Types</code> class
250: * @throws InvalidDataAccessApiUsageException if the operation is already compiled
251: */
252: public void setTypes(int[] types)
253: throws InvalidDataAccessApiUsageException {
254: if (isCompiled()) {
255: throw new InvalidDataAccessApiUsageException(
256: "Cannot add parameters once query is compiled");
257: }
258: if (types != null) {
259: for (int i = 0; i < types.length; i++) {
260: declareParameter(new SqlParameter(types[i]));
261: }
262: }
263: }
264:
265: /**
266: * Declare a parameter for this operation.
267: * <p>The order in which this method is called is significant when using
268: * positional parameters. It is not significant when using named parameters
269: * with named SqlParameter objects here; it remains significant when using
270: * named parameters in combination with unnamed SqlParameter objects here.
271: * @param param the SqlParameter to add. This will specify SQL type and (optionally)
272: * the parameter's name. Note that you typically use the {@link SqlParameter} class
273: * itself here, not any of its subclasses.
274: * @throws InvalidDataAccessApiUsageException if the operation is already compiled,
275: * and hence cannot be configured further
276: */
277: public void declareParameter(SqlParameter param)
278: throws InvalidDataAccessApiUsageException {
279: if (isCompiled()) {
280: throw new InvalidDataAccessApiUsageException(
281: "Cannot add parameters once the query is compiled");
282: }
283: this .declaredParameters.add(param);
284: }
285:
286: /**
287: * Add one or more declared parameters. Used for configuring this operation
288: * when used in a bean factory. Each parameter will specify SQL type and (optionally)
289: * the parameter's name.
290: * @param parameters Array containing the declared {@link SqlParameter} objects
291: * @see #declaredParameters
292: */
293: public void setParameters(SqlParameter[] parameters) {
294: if (isCompiled()) {
295: throw new InvalidDataAccessApiUsageException(
296: "Cannot add parameters once the query is compiled");
297: }
298: for (int i = 0; i < parameters.length; i++) {
299: if (parameters[i] != null) {
300: this .declaredParameters.add(parameters[i]);
301: } else {
302: throw new InvalidDataAccessApiUsageException(
303: "Cannot add parameter at index " + i + " from "
304: + Arrays.asList(parameters)
305: + " since it is 'null'");
306: }
307: }
308: }
309:
310: /**
311: * Return a list of the declared {@link SqlParameter} objects.
312: */
313: protected List getDeclaredParameters() {
314: return this .declaredParameters;
315: }
316:
317: /**
318: * Ensures compilation if used in a bean factory.
319: */
320: public void afterPropertiesSet() {
321: compile();
322: }
323:
324: /**
325: * Compile this query.
326: * Ignores subsequent attempts to compile.
327: * @throws InvalidDataAccessApiUsageException if the object hasn't
328: * been correctly initialized, for example if no DataSource has been provided
329: */
330: public final void compile()
331: throws InvalidDataAccessApiUsageException {
332: if (!isCompiled()) {
333: if (getSql() == null) {
334: throw new InvalidDataAccessApiUsageException(
335: "Property 'sql' is required");
336: }
337:
338: try {
339: this .jdbcTemplate.afterPropertiesSet();
340: } catch (IllegalArgumentException ex) {
341: throw new InvalidDataAccessApiUsageException(ex
342: .getMessage());
343: }
344:
345: compileInternal();
346: this .compiled = true;
347:
348: if (logger.isDebugEnabled()) {
349: logger.debug("RdbmsOperation with SQL [" + getSql()
350: + "] compiled");
351: }
352: }
353: }
354:
355: /**
356: * Is this operation "compiled"? Compilation, as in JDO,
357: * means that the operation is fully configured, and ready to use.
358: * The exact meaning of compilation will vary between subclasses.
359: * @return whether this operation is compiled, and ready to use.
360: */
361: public boolean isCompiled() {
362: return this .compiled;
363: }
364:
365: /**
366: * Check whether this operation has been compiled already;
367: * lazily compile it if not already compiled.
368: * <p>Automatically called by <code>validateParameters</code>.
369: * @see #validateParameters
370: */
371: protected void checkCompiled() {
372: if (!isCompiled()) {
373: logger
374: .debug("SQL operation not compiled before execution - invoking compile");
375: compile();
376: }
377: }
378:
379: /**
380: * Validate the parameters passed to an execute method based on declared parameters.
381: * Subclasses should invoke this method before every <code>executeQuery()</code>
382: * or <code>update()</code> method.
383: * @param parameters parameters supplied (may be <code>null</code>)
384: * @throws InvalidDataAccessApiUsageException if the parameters are invalid
385: */
386: protected void validateParameters(Object[] parameters)
387: throws InvalidDataAccessApiUsageException {
388: checkCompiled();
389:
390: int declaredInParameters = 0;
391: Iterator it = this .declaredParameters.iterator();
392: while (it.hasNext()) {
393: SqlParameter param = (SqlParameter) it.next();
394: if (param.isInputValueProvided()) {
395: if (!supportsLobParameters()
396: && (param.getSqlType() == Types.BLOB || param
397: .getSqlType() == Types.CLOB)) {
398: throw new InvalidDataAccessApiUsageException(
399: "BLOB or CLOB parameters are not allowed for this kind of operation");
400: }
401: declaredInParameters++;
402: }
403: }
404:
405: validateParameterCount((parameters != null ? parameters.length
406: : 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()</code> or
412: * <code>update()</code> method.
413: * @param parameters parameter Map supplied. May be <code>null</code>.
414: * @throws InvalidDataAccessApiUsageException if the parameters are invalid
415: */
416: protected void validateNamedParameters(Map parameters)
417: throws InvalidDataAccessApiUsageException {
418: checkCompiled();
419: Map paramsToUse = (parameters != null ? parameters
420: : Collections.EMPTY_MAP);
421:
422: int declaredInParameters = 0;
423: Iterator it = this .declaredParameters.iterator();
424: while (it.hasNext()) {
425: SqlParameter param = (SqlParameter) it.next();
426: if (param.isInputValueProvided()) {
427: if (!supportsLobParameters()
428: && (param.getSqlType() == Types.BLOB || param
429: .getSqlType() == Types.CLOB)) {
430: throw new InvalidDataAccessApiUsageException(
431: "BLOB or CLOB parameters are not allowed for this kind of operation");
432: }
433: if (param.getName() != null
434: && !paramsToUse.containsKey(param.getName())) {
435: throw new InvalidDataAccessApiUsageException(
436: "The parameter named '"
437: + param.getName()
438: + "' was not among the parameters supplied: "
439: + paramsToUse.keySet());
440: }
441: declaredInParameters++;
442: }
443: }
444:
445: validateParameterCount(paramsToUse.size(), declaredInParameters);
446: }
447:
448: /**
449: * Validate the given parameter count against the given declared parameters.
450: * @param suppliedParamCount the number of actual parameters given
451: * @param declaredInParamCount the number of input parameters declared
452: */
453: private void validateParameterCount(int suppliedParamCount,
454: int declaredInParamCount) {
455: if (suppliedParamCount < declaredInParamCount) {
456: throw new InvalidDataAccessApiUsageException(
457: suppliedParamCount
458: + " parameters were supplied, but "
459: + declaredInParamCount
460: + " in parameters were declared in class ["
461: + getClass().getName() + "]");
462: }
463: if (suppliedParamCount > this .declaredParameters.size()
464: && !allowsUnusedParameters()) {
465: throw new InvalidDataAccessApiUsageException(
466: suppliedParamCount
467: + " parameters were supplied, but "
468: + declaredInParamCount
469: + " parameters were declared in class ["
470: + getClass().getName() + "]");
471: }
472: }
473:
474: /**
475: * Subclasses must implement this template method to perform their own compilation.
476: * Invoked after this base class's compilation is complete.
477: * <p>Subclasses can assume that SQL and a DataSource have been supplied.
478: * @throws InvalidDataAccessApiUsageException if the subclass hasn't been
479: * properly configured
480: */
481: protected abstract void compileInternal()
482: throws InvalidDataAccessApiUsageException;
483:
484: /**
485: * Return whether BLOB/CLOB parameters are supported for this kind of operation.
486: * <p>The default is <code>true</code>.
487: */
488: protected boolean supportsLobParameters() {
489: return true;
490: }
491:
492: /**
493: * Return whether this operation accepts additional parameters that are
494: * given but not actually used. Applies in particular to parameter Maps.
495: * <p>The default is <code>false</code>.
496: * @see StoredProcedure
497: */
498: protected boolean allowsUnusedParameters() {
499: return false;
500: }
501:
502: }
|