1. /*
  2. * Copyright 2002-2004 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. package org.springframework.jdbc.object;
  17. import java.util.Iterator;
  18. import java.util.LinkedList;
  19. import java.util.List;
  20. import javax.sql.DataSource;
  21. import org.apache.commons.logging.Log;
  22. import org.apache.commons.logging.LogFactory;
  23. import org.springframework.beans.factory.InitializingBean;
  24. import org.springframework.dao.InvalidDataAccessApiUsageException;
  25. import org.springframework.jdbc.core.JdbcTemplate;
  26. import org.springframework.jdbc.core.SqlOutParameter;
  27. import org.springframework.jdbc.core.SqlParameter;
  28. import org.springframework.jdbc.core.SqlReturnResultSet;
  29. /**
  30. * Root of the JDBC object hierarchy, as described in Chapter 9 of
  31. * <a href="http://www.amazon.com/exec/obidos/tg/detail/-/0764543857/">
  32. * Expert One-On-One J2EE Design and Development</a> by Rod Johnson (Wrox, 2002).
  33. *
  34. * <p>An "RDBMS operation" is a multithreaded, reusable object representing
  35. * a query, update or stored procedure. An RDBMS operation is <b>not</b> a command,
  36. * as a command isn't reusable. However, execute methods may take commands as
  37. * arguments. Subclasses should be Java beans, allowing easy configuration.
  38. *
  39. * <p>This class and subclasses throw runtime exceptions, defined in the
  40. * org.springframework.dao package (and as thrown by the org.springframework.jdbc.core
  41. * package, which the classes in this package use to perform raw JDBC actions).
  42. *
  43. * <p>Subclasses should set SQL and add parameters before invoking the
  44. * compile() method. The order in which parameters are added is significant.
  45. * The appropriate execute or update method can then be invoked.
  46. *
  47. * @author Rod Johnson
  48. * @version $Id: RdbmsOperation.java,v 1.9 2004/05/27 14:46:26 jhoeller Exp $
  49. * @see org.springframework.dao
  50. * @see org.springframework.jdbc.core
  51. */
  52. public abstract class RdbmsOperation implements InitializingBean {
  53. protected final Log logger = LogFactory.getLog(getClass());
  54. /** Lower-level class used to execute SQL */
  55. private JdbcTemplate jdbcTemplate = new JdbcTemplate();
  56. /** SQL statement */
  57. private String sql;
  58. /** List of SqlParameter objects */
  59. private List declaredParameters = new LinkedList();
  60. /**
  61. * Has this operation been compiled? Compilation means at
  62. * least checking that a DataSource and sql have been provided,
  63. * but subclasses may also implement their own custom validation.
  64. */
  65. private boolean compiled;
  66. /**
  67. * Set the JDBC DataSource to obtain connections from.
  68. */
  69. public void setDataSource(DataSource dataSource) {
  70. this.jdbcTemplate.setDataSource(dataSource);
  71. }
  72. /**
  73. * An alternative to the more commonly used setDataSource() when you want to
  74. * use the same JdbcTemplate in multiple RdbmsOperations. This is appropriate if the
  75. * JdbcTemplate has special configuration such as a SQLExceptionTranslator that should
  76. * apply to multiple RdbmsOperation objects.
  77. * @param jdbcTemplate
  78. */
  79. public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
  80. this.jdbcTemplate = jdbcTemplate;
  81. }
  82. /**
  83. * Return the JdbcTemplate object used by this object.
  84. */
  85. protected JdbcTemplate getJdbcTemplate() {
  86. return jdbcTemplate;
  87. }
  88. /**
  89. * Set the SQL executed by this operation.
  90. */
  91. public void setSql(String sql) {
  92. this.sql = sql;
  93. }
  94. /**
  95. * Subclasses can override this to supply dynamic SQL if they wish,
  96. * but SQL is normally set by calling the setSql() method
  97. * or in a subclass constructor.
  98. */
  99. public String getSql() {
  100. return sql;
  101. }
  102. /**
  103. * Add anonymous parameters, specifying only their SQL types
  104. * as defined in the java.sql.Types class.
  105. * <p>Parameter ordering is significant. This method is an alternative
  106. * to the declareParameter() method, which should normally be preferred.
  107. * @param types array of SQL types as defined in the
  108. * java.sql.Types class
  109. * @throws InvalidDataAccessApiUsageException if the operation is already compiled
  110. */
  111. public void setTypes(int[] types) throws InvalidDataAccessApiUsageException {
  112. if (this.compiled) {
  113. throw new InvalidDataAccessApiUsageException("Cannot add parameters once query is compiled");
  114. }
  115. if (types != null) {
  116. for (int i = 0; i < types.length; i++) {
  117. declareParameter(new SqlParameter(types[i]));
  118. }
  119. }
  120. }
  121. /**
  122. * Declare a parameter. The order in which this method is called is significant.
  123. * @param param SqlParameter to add. This will specify SQL type and (optionally)
  124. * the parameter's name.
  125. * @throws InvalidDataAccessApiUsageException if the operation is already compiled,
  126. * and hence cannot be configured further
  127. */
  128. public void declareParameter(SqlParameter param) throws InvalidDataAccessApiUsageException {
  129. if (this.compiled) {
  130. throw new InvalidDataAccessApiUsageException("Cannot add parameters once query is compiled");
  131. }
  132. this.declaredParameters.add(param);
  133. }
  134. /**
  135. * Return a list of the declared SqlParameter objects.
  136. */
  137. protected List getDeclaredParameters() {
  138. return declaredParameters;
  139. }
  140. /**
  141. * Ensures compilation if used in a bean factory.
  142. */
  143. public void afterPropertiesSet() {
  144. compile();
  145. }
  146. /**
  147. * Is this operation "compiled"? Compilation, as in JDO,
  148. * means that the operation is fully configured, and ready to use.
  149. * The exact meaning of compilation will vary between subclasses.
  150. * @return whether this operation is compiled, and ready to use.
  151. */
  152. public boolean isCompiled() {
  153. return compiled;
  154. }
  155. /**
  156. * Compile this query.
  157. * Ignore subsequent attempts to compile
  158. * @throws InvalidDataAccessApiUsageException if the object hasn't
  159. * been correctly initialized, for example if no DataSource has been provided.
  160. */
  161. public final void compile() throws InvalidDataAccessApiUsageException {
  162. if (!isCompiled()) {
  163. if (getSql() == null) {
  164. throw new InvalidDataAccessApiUsageException("sql must be set in class " + getClass().getName());
  165. }
  166. try {
  167. this.jdbcTemplate.afterPropertiesSet();
  168. }
  169. catch (IllegalArgumentException ex) {
  170. throw new InvalidDataAccessApiUsageException(ex.getMessage());
  171. }
  172. compileInternal();
  173. this.compiled = true;
  174. logger.info("RdbmsOperation with SQL [" + getSql() + "] compiled");
  175. }
  176. }
  177. /**
  178. * Subclasses must implement to perform their own compilation.
  179. * Invoked after this class's compilation is complete.
  180. * Subclasses can assume that sql has been supplied and that
  181. * a DataSource has been supplied.
  182. * @throws InvalidDataAccessApiUsageException if the subclass
  183. * hasn't been properly configured.
  184. */
  185. protected abstract void compileInternal() throws InvalidDataAccessApiUsageException;
  186. /**
  187. * Validate the parameters passed to an execute method based on declared parameters.
  188. * Subclasses should invoke this method before every executeQuery() or update() method.
  189. * @param parameters parameters supplied. May be null.
  190. * @throws InvalidDataAccessApiUsageException if the parameters are invalid
  191. */
  192. protected void validateParameters(Object[] parameters) throws InvalidDataAccessApiUsageException {
  193. if (!this.compiled) {
  194. logger.info("SQL operation not compiled before execution - invoking compile");
  195. compile();
  196. }
  197. int declaredInParameters = 0;
  198. if (this.declaredParameters != null) {
  199. Iterator iter = this.declaredParameters.iterator();
  200. while (iter.hasNext()) {
  201. Object param = iter.next();
  202. if (!(param instanceof SqlOutParameter) && !(param instanceof SqlReturnResultSet)) {
  203. declaredInParameters++;
  204. }
  205. }
  206. }
  207. if (parameters != null) {
  208. if (this.declaredParameters == null) {
  209. throw new InvalidDataAccessApiUsageException("Didn't expect any parameters: none was declared");
  210. }
  211. if (parameters.length < declaredInParameters) {
  212. throw new InvalidDataAccessApiUsageException(parameters.length + " parameters were supplied, but " +
  213. declaredInParameters + " in parameters were declared in class [" +
  214. getClass().getName() + "]");
  215. }
  216. if (parameters.length > this.declaredParameters.size()) {
  217. throw new InvalidDataAccessApiUsageException(parameters.length + " parameters were supplied, but " +
  218. this.declaredParameters.size() + " parameters were declared " +
  219. "in class [" + getClass().getName() + "]");
  220. }
  221. }
  222. else {
  223. // no parameters were supplied
  224. if (!this.declaredParameters.isEmpty()) {
  225. throw new InvalidDataAccessApiUsageException(this.declaredParameters.size() + " parameters must be supplied");
  226. }
  227. }
  228. }
  229. }