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