KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > jdbc > object > RdbmsOperation


1 /*
2  * Copyright 2002-2006 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.jdbc.object;
18
19 import java.sql.ResultSet JavaDoc;
20 import java.sql.Types JavaDoc;
21 import java.util.Iterator JavaDoc;
22 import java.util.LinkedList JavaDoc;
23 import java.util.List JavaDoc;
24 import java.util.Map JavaDoc;
25
26 import javax.sql.DataSource JavaDoc;
27
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30
31 import org.springframework.beans.factory.InitializingBean;
32 import org.springframework.dao.InvalidDataAccessApiUsageException;
33 import org.springframework.jdbc.core.JdbcTemplate;
34 import org.springframework.jdbc.core.ResultSetSupportingSqlParameter;
35 import org.springframework.jdbc.core.SqlParameter;
36
37 /**
38  * An "RDBMS operation" is a multithreaded, reusable object representing a
39  * query, update or stored procedure. An RDBMS operation is <b>not</b> a command,
40  * as a command isn't reusable. However, execute methods may take commands as
41  * arguments. Subclasses should be Java beans, allowing easy configuration.
42  *
43  * <p>Root of the JDBC object hierarchy, as described in Chapter 9 of
44  * <a HREF="http://www.amazon.com/exec/obidos/tg/detail/-/0764543857/">
45  * Expert One-On-One J2EE Design and Development</a> by Rod Johnson (Wrox, 2002).
46  *
47  * <p>This class and subclasses throw runtime exceptions, defined in the
48  * <codeorg.springframework.dao package</code> (and as thrown by the
49  * <code>org.springframework.jdbc.core</code> package, which the classes
50  * in this package use under the hood to perform raw JDBC operations).
51  *
52  * <p>Subclasses should set SQL and add parameters before invoking the
53  * <code>compile()</code> method. The order in which parameters are added is
54  * significant. The appropriate <code>execute</code> or <code>update</code>
55  * method can then be invoked.
56  *
57  * @author Rod Johnson
58  * @author Juergen Hoeller
59  * @see #compile
60  * @see org.springframework.dao
61  * @see org.springframework.jdbc.core
62  */

63 public abstract class RdbmsOperation implements InitializingBean {
64
65     protected final Log logger = LogFactory.getLog(getClass());
66
67     /** Lower-level class used to execute SQL */
68     private JdbcTemplate jdbcTemplate = new JdbcTemplate();
69
70     private int resultSetType = ResultSet.TYPE_FORWARD_ONLY;
71
72     private boolean updatableResults = false;
73
74     private boolean returnGeneratedKeys = false;
75     
76     private String JavaDoc[] generatedKeysColumnNames = null;
77
78     /** SQL statement */
79     private String JavaDoc sql;
80
81     /** List of SqlParameter objects */
82     private List JavaDoc declaredParameters = new LinkedList JavaDoc();
83
84     /**
85      * Has this operation been compiled? Compilation means at
86      * least checking that a DataSource and sql have been provided,
87      * but subclasses may also implement their own custom validation.
88      */

89     private boolean compiled;
90     
91     
92     /**
93      * An alternative to the more commonly used setDataSource() when you want to
94      * use the same JdbcTemplate in multiple RdbmsOperations. This is appropriate if the
95      * JdbcTemplate has special configuration such as a SQLExceptionTranslator that should
96      * apply to multiple RdbmsOperation objects.
97      */

98     public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
99         if (jdbcTemplate == null) {
100             throw new IllegalArgumentException JavaDoc("jdbcTemplate must not be null");
101         }
102         this.jdbcTemplate = jdbcTemplate;
103     }
104
105     /**
106      * Return the JdbcTemplate object used by this object.
107      */

108     public JdbcTemplate getJdbcTemplate() {
109         return jdbcTemplate;
110     }
111
112     /**
113      * Set the JDBC DataSource to obtain connections from.
114      * @see org.springframework.jdbc.core.JdbcTemplate#setDataSource
115      */

116     public void setDataSource(DataSource JavaDoc dataSource) {
117         this.jdbcTemplate.setDataSource(dataSource);
118     }
119
120     /**
121      * Set the fetch size for this RDBMS operation. This is important for processing
122      * large result sets: Setting this higher than the default value will increase
123      * processing speed at the cost of memory consumption; setting this lower can
124      * avoid transferring row data that will never be read by the application.
125      * <p>Default is 0, indicating to use the driver's default.
126      * @see org.springframework.jdbc.core.JdbcTemplate#setFetchSize
127      */

128     public void setFetchSize(int fetchSize) {
129         this.jdbcTemplate.setFetchSize(fetchSize);
130     }
131
132     /**
133      * Set the maximum number of rows for this RDBMS operation. This is important
134      * for processing subsets of large result sets, avoiding to read and hold
135      * the entire result set in the database or in the JDBC driver.
136      * <p>Default is 0, indicating to use the driver's default.
137      * @see org.springframework.jdbc.core.JdbcTemplate#setMaxRows
138      */

139     public void setMaxRows(int maxRows) {
140         this.jdbcTemplate.setMaxRows(maxRows);
141     }
142
143     /**
144      * Set the query timeout for statements that this RDBMS operation executes.
145      * <p>Default is 0, indicating to use the JDBC driver's default.
146      * <p>Note: Any timeout specified here will be overridden by the remaining
147      * transaction timeout when executing within a transaction that has a
148      * timeout specified at the transaction level.
149      */

150     public void setQueryTimeout(int queryTimeout) {
151         this.jdbcTemplate.setQueryTimeout(queryTimeout);
152     }
153
154     /**
155      * Set whether to use statements that return a specific type of ResultSet.
156      * @param resultSetType the ResultSet type
157      * @see java.sql.ResultSet#TYPE_FORWARD_ONLY
158      * @see java.sql.ResultSet#TYPE_SCROLL_INSENSITIVE
159      * @see java.sql.ResultSet#TYPE_SCROLL_SENSITIVE
160      * @see java.sql.Connection#prepareStatement(String, int, int)
161      */

162     public void setResultSetType(int resultSetType) {
163         this.resultSetType = resultSetType;
164     }
165
166     /**
167      * Return whether statements will return a specific type of ResultSet.
168      */

169     public int getResultSetType() {
170         return resultSetType;
171     }
172
173     /**
174      * Set whether to use statements that are capable of returning
175      * updatable ResultSets.
176      * @see java.sql.Connection#prepareStatement(String, int, int)
177      */

178     public void setUpdatableResults(boolean updatableResults) {
179         if (isCompiled()) {
180             throw new InvalidDataAccessApiUsageException(
181                     "The updateableResults flag must be set before the operation is compiled");
182         }
183         this.updatableResults = updatableResults;
184     }
185
186     /**
187      * Return whether statements will return updatable ResultSets.
188      */

189     public boolean isUpdatableResults() {
190         return updatableResults;
191     }
192
193     /**
194      * Set whether prepared statements should be capable of returning
195      * auto-generated keys.
196      * @see java.sql.Connection#prepareStatement(String, int)
197      */

198     public void setReturnGeneratedKeys(boolean returnGeneratedKeys) {
199         if (isCompiled()) {
200             throw new InvalidDataAccessApiUsageException(
201                     "The returnGeneratedKeys flag must be set before the operation is compiled");
202         }
203         this.returnGeneratedKeys = returnGeneratedKeys;
204     }
205
206     /**
207      * Return whether statements should be capable of returning
208      * auto-generated keys.
209      */

210     public boolean isReturnGeneratedKeys() {
211         return returnGeneratedKeys;
212     }
213
214     /**
215      * Set the column names of the auto-generated keys.
216      * @see java.sql.Connection#prepareStatement(String, String[])
217      */

218     public void setGeneratedKeysColumnNames(String JavaDoc[] names) {
219         if (isCompiled()) {
220             throw new InvalidDataAccessApiUsageException(
221                     "The column names for the generated keys must be set before the operation is compiled");
222         }
223         this.generatedKeysColumnNames = names;
224     }
225
226     /**
227      * Return the column names of the auto generated keys.
228      */

229     public String JavaDoc[] getGeneratedKeysColumnNames() {
230         return generatedKeysColumnNames;
231     }
232
233     /**
234      * Set the SQL executed by this operation.
235      */

236     public void setSql(String JavaDoc sql) {
237         this.sql = sql;
238     }
239
240     /**
241      * Subclasses can override this to supply dynamic SQL if they wish,
242      * but SQL is normally set by calling the setSql() method
243      * or in a subclass constructor.
244      */

245     public String JavaDoc getSql() {
246         return sql;
247     }
248
249     /**
250      * Add anonymous parameters, specifying only their SQL types
251      * as defined in the <code>java.sql.Types</code> class.
252      * <p>Parameter ordering is significant. This method is an alternative
253      * to the declareParameter() method, which should normally be preferred.
254      * @param types array of SQL types as defined in the
255      * <code>java.sql.Types</code> class
256      * @throws InvalidDataAccessApiUsageException if the operation is already compiled
257      */

258     public void setTypes(int[] types) throws InvalidDataAccessApiUsageException {
259         if (isCompiled()) {
260             throw new InvalidDataAccessApiUsageException("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. The order in which this method is called is significant.
271      * @param param SqlParameter to add. This will specify SQL type and (optionally)
272      * the parameter's name.
273      * @throws InvalidDataAccessApiUsageException if the operation is already compiled,
274      * and hence cannot be configured further
275      */

276     public void declareParameter(SqlParameter param) throws InvalidDataAccessApiUsageException {
277         if (isCompiled()) {
278             throw new InvalidDataAccessApiUsageException("Cannot add parameters once query is compiled");
279         }
280         this.declaredParameters.add(param);
281     }
282
283     /**
284      * Return a list of the declared SqlParameter objects.
285      */

286     protected List JavaDoc getDeclaredParameters() {
287         return declaredParameters;
288     }
289
290
291     /**
292      * Ensures compilation if used in a bean factory.
293      */

294     public void afterPropertiesSet() {
295         compile();
296     }
297
298     /**
299      * Compile this query.
300      * Ignores subsequent attempts to compile.
301      * @throws InvalidDataAccessApiUsageException if the object hasn't
302      * been correctly initialized, for example if no DataSource has been provided
303      */

304     public final void compile() throws InvalidDataAccessApiUsageException {
305         if (!isCompiled()) {
306             if (getSql() == null) {
307                 throw new InvalidDataAccessApiUsageException("sql is required");
308             }
309
310             try {
311                 this.jdbcTemplate.afterPropertiesSet();
312             }
313             catch (IllegalArgumentException JavaDoc ex) {
314                 throw new InvalidDataAccessApiUsageException(ex.getMessage());
315             }
316         
317             compileInternal();
318             this.compiled = true;
319
320             if (logger.isDebugEnabled()) {
321                 logger.debug("RdbmsOperation with SQL [" + getSql() + "] compiled");
322             }
323         }
324     }
325
326     /**
327      * Subclasses must implement to perform their own compilation.
328      * Invoked after this class's compilation is complete.
329      * <p>Subclasses can assume that SQL has been supplied and that
330      * a DataSource has been supplied.
331      * @throws InvalidDataAccessApiUsageException if the subclass
332      * hasn't been properly configured.
333      */

334     protected abstract void compileInternal() throws InvalidDataAccessApiUsageException;
335
336     /**
337      * Is this operation "compiled"? Compilation, as in JDO,
338      * means that the operation is fully configured, and ready to use.
339      * The exact meaning of compilation will vary between subclasses.
340      * @return whether this operation is compiled, and ready to use.
341      */

342     public boolean isCompiled() {
343         return compiled;
344     }
345
346     /**
347      * Check whether this operation has been compiled already;
348      * lazily compile it if not already compiled.
349      * <p>Automatically called by <code>validateParameters</code>.
350      * @see #validateParameters
351      */

352     protected void checkCompiled() {
353         if (!isCompiled()) {
354             logger.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 JavaDoc[] parameters) throws InvalidDataAccessApiUsageException {
367         checkCompiled();
368
369         int declaredInParameters = 0;
370         if (this.declaredParameters != null) {
371             Iterator JavaDoc it = this.declaredParameters.iterator();
372             while (it.hasNext()) {
373                 Object JavaDoc param = it.next();
374                 if (!(param instanceof ResultSetSupportingSqlParameter)) {
375                     if (!supportsLobParameters() &&
376                             (((SqlParameter) param).getSqlType() == Types.BLOB ||
377                             ((SqlParameter) param).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
386         if (parameters != null) {
387             if (this.declaredParameters == null) {
388                 throw new InvalidDataAccessApiUsageException("Didn't expect any parameters: none was declared");
389             }
390             if (parameters.length < declaredInParameters) {
391                 throw new InvalidDataAccessApiUsageException(
392                         parameters.length + " parameters were supplied, but " +
393                         declaredInParameters + " in parameters were declared in class [" +
394                         getClass().getName() + "]");
395             }
396             if (!allowsUnusedParameters() && parameters.length > this.declaredParameters.size()) {
397                 throw new InvalidDataAccessApiUsageException(
398                         parameters.length + " parameters were supplied, but " +
399                         this.declaredParameters.size() + " parameters were declared " +
400                         "in class [" + getClass().getName() + "]");
401             }
402         }
403         else {
404             // No parameters were supplied.
405
if (this.declaredParameters != null && !this.declaredParameters.isEmpty()) {
406                 throw new InvalidDataAccessApiUsageException(
407                         this.declaredParameters.size() + " parameters must be supplied");
408             }
409         }
410     }
411
412     /**
413      * Validate the named parameters passed to an execute method based on declared parameters.
414      * Subclasses should invoke this method before every <code>executeQuery()</code> or
415      * <code>update()</code> method.
416      * @param parameters parameter Map supplied. May be <code>null</code>.
417      * @throws InvalidDataAccessApiUsageException if the parameters are invalid
418      */

419     protected void validateNamedParameters(Map JavaDoc parameters) throws InvalidDataAccessApiUsageException {
420         checkCompiled();
421
422         if (this.declaredParameters != null) {
423             Iterator JavaDoc it = this.declaredParameters.iterator();
424             while (it.hasNext()) {
425                 Object JavaDoc param = it.next();
426                 if (!(param instanceof ResultSetSupportingSqlParameter)) {
427                     if (!supportsLobParameters() &&
428                             (((SqlParameter) param).getSqlType() == Types.BLOB ||
429                             ((SqlParameter) param).getSqlType() == Types.CLOB)) {
430                         throw new InvalidDataAccessApiUsageException(
431                                 "BLOB or CLOB parameters are not allowed for this kind of operation");
432                     }
433                     if (((SqlParameter) param).getName() == null) {
434                         throw new InvalidDataAccessApiUsageException(
435                                 "All parameters must have name specified when using the methods " +
436                                     "dedicated to named parameter support");
437                     }
438                     if (!parameters.containsKey(((SqlParameter) param).getName())) {
439                         throw new InvalidDataAccessApiUsageException(
440                                 "The parameter named '" + ((SqlParameter)param).getName() +
441                                     "' were not among the parameters supplied: " +
442                                     parameters.keySet());
443                     }
444                 }
445             }
446         }
447
448         if (parameters != null && parameters.size() > 0) {
449             if (this.declaredParameters == null) {
450                 throw new InvalidDataAccessApiUsageException("Didn't expect any parameters: none was declared");
451             }
452         }
453         else {
454             // No parameters were supplied.
455
if (this.declaredParameters != null && !this.declaredParameters.isEmpty()) {
456                 throw new InvalidDataAccessApiUsageException("Parameters must be supplied");
457             }
458         }
459     }
460
461     /**
462      * Return whether BLOB or CLOB parameters are supported
463      * for this kind of operation. Default is "true".
464      */

465     protected boolean supportsLobParameters() {
466         return true;
467     }
468
469     /**
470      * Return whether this operation accepts additional parameters that are
471      * given but not actually used. Applies in particular to parameter Maps.
472      * @see StoredProcedure
473      */

474     protected boolean allowsUnusedParameters() {
475         return false;
476     }
477
478 }
479
Popular Tags