View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.dbutils;
18  
19  import java.beans.IntrospectionException;
20  import java.beans.Introspector;
21  import java.beans.PropertyDescriptor;
22  import java.lang.reflect.InvocationTargetException;
23  import java.lang.reflect.Method;
24  import java.sql.Connection;
25  import java.sql.ParameterMetaData;
26  import java.sql.PreparedStatement;
27  import java.sql.ResultSet;
28  import java.sql.SQLException;
29  import java.sql.Statement;
30  import java.sql.Types;
31  import java.util.Arrays;
32  
33  import javax.sql.DataSource;
34  
35  /**
36   * The base class for QueryRunner & AsyncQueryRunner. This class is thread safe.
37   *
38   * @since 1.4 (mostly extracted from QueryRunner)
39   */
40  public abstract class AbstractQueryRunner {
41      /**
42       * Is {@link ParameterMetaData#getParameterType(int)} broken (have we tried
43       * it yet)?
44       */
45      private volatile boolean pmdKnownBroken = false;
46  
47      /**
48       * The DataSource to retrieve connections from.
49       * @deprecated Access to this field should be through {@link #getDataSource()}.
50       */
51      @Deprecated
52      protected final DataSource ds;
53  
54      /**
55       * Default constructor, sets pmdKnownBroken to false and ds to null.
56       */
57      public AbstractQueryRunner() {
58          ds = null;
59      }
60  
61      /**
62       * Constructor to control the use of <code>ParameterMetaData</code>.
63       *
64       * @param pmdKnownBroken
65       *            Some drivers don't support
66       *            {@link ParameterMetaData#getParameterType(int) }; if
67       *            <code>pmdKnownBroken</code> is set to true, we won't even try
68       *            it; if false, we'll try it, and if it breaks, we'll remember
69       *            not to use it again.
70       */
71      public AbstractQueryRunner(boolean pmdKnownBroken) {
72          this.pmdKnownBroken = pmdKnownBroken;
73          ds = null;
74      }
75  
76      /**
77       * Constructor to provide a <code>DataSource</code>. Methods that do not
78       * take a <code>Connection</code> parameter will retrieve connections from
79       * this <code>DataSource</code>.
80       *
81       * @param ds
82       *            The <code>DataSource</code> to retrieve connections from.
83       */
84      public AbstractQueryRunner(DataSource ds) {
85          this.ds = ds;
86      }
87  
88      /**
89       * Constructor to provide a <code>DataSource</code> and control the use of
90       * <code>ParameterMetaData</code>. Methods that do not take a
91       * <code>Connection</code> parameter will retrieve connections from this
92       * <code>DataSource</code>.
93       *
94       * @param ds
95       *            The <code>DataSource</code> to retrieve connections from.
96       * @param pmdKnownBroken
97       *            Some drivers don't support
98       *            {@link ParameterMetaData#getParameterType(int) }; if
99       *            <code>pmdKnownBroken</code> is set to true, we won't even try
100      *            it; if false, we'll try it, and if it breaks, we'll remember
101      *            not to use it again.
102      */
103     public AbstractQueryRunner(DataSource ds, boolean pmdKnownBroken) {
104         this.pmdKnownBroken = pmdKnownBroken;
105         this.ds = ds;
106     }
107 
108     /**
109      * Returns the <code>DataSource</code> this runner is using.
110      * <code>QueryRunner</code> methods always call this method to get the
111      * <code>DataSource</code> so subclasses can provide specialized behavior.
112      *
113      * @return DataSource the runner is using
114      */
115     public DataSource getDataSource() {
116         return this.ds;
117     }
118 
119     /**
120      * Some drivers don't support
121      * {@link ParameterMetaData#getParameterType(int) }; if
122      * <code>pmdKnownBroken</code> is set to true, we won't even try it; if
123      * false, we'll try it, and if it breaks, we'll remember not to use it
124      * again.
125      *
126      * @return the flag to skip (or not)
127      *         {@link ParameterMetaData#getParameterType(int) }
128      * @since 1.4
129      */
130     public boolean isPmdKnownBroken() {
131         return pmdKnownBroken;
132     }
133 
134     /**
135      * Factory method that creates and initializes a
136      * <code>PreparedStatement</code> object for the given SQL.
137      * <code>QueryRunner</code> methods always call this method to prepare
138      * statements for them. Subclasses can override this method to provide
139      * special PreparedStatement configuration if needed. This implementation
140      * simply calls <code>conn.prepareStatement(sql)</code>.
141      *
142      * @param conn
143      *            The <code>Connection</code> used to create the
144      *            <code>PreparedStatement</code>
145      * @param sql
146      *            The SQL statement to prepare.
147      * @return An initialized <code>PreparedStatement</code>.
148      * @throws SQLException
149      *             if a database access error occurs
150      */
151     protected PreparedStatement prepareStatement(Connection conn, String sql)
152             throws SQLException {
153 
154         return conn.prepareStatement(sql);
155     }
156 
157     /**
158      * Factory method that creates and initializes a
159      * <code>PreparedStatement</code> object for the given SQL.
160      * <code>QueryRunner</code> methods always call this method to prepare
161      * statements for them. Subclasses can override this method to provide
162      * special PreparedStatement configuration if needed. This implementation
163      * simply calls <code>conn.prepareStatement(sql, returnedKeys)</code>
164      * which will result in the ability to retrieve the automatically-generated
165      * keys from an auto_increment column.
166      *
167      * @param conn
168      *            The <code>Connection</code> used to create the
169      *            <code>PreparedStatement</code>
170      * @param sql
171      *            The SQL statement to prepare.
172      * @param returnedKeys
173      *            Flag indicating whether to return generated keys or not.
174      *
175      * @return An initialized <code>PreparedStatement</code>.
176      * @throws SQLException
177      *             if a database access error occurs
178      * @since 1.6
179      */
180     protected PreparedStatement prepareStatement(Connection conn, String sql, int returnedKeys)
181             throws SQLException {
182 
183         return conn.prepareStatement(sql, returnedKeys);
184     }
185 
186     /**
187      * Factory method that creates and initializes a <code>Connection</code>
188      * object. <code>QueryRunner</code> methods always call this method to
189      * retrieve connections from its DataSource. Subclasses can override this
190      * method to provide special <code>Connection</code> configuration if
191      * needed. This implementation simply calls <code>ds.getConnection()</code>.
192      *
193      * @return An initialized <code>Connection</code>.
194      * @throws SQLException
195      *             if a database access error occurs
196      * @since DbUtils 1.1
197      */
198     protected Connection prepareConnection() throws SQLException {
199         if (this.getDataSource() == null) {
200             throw new SQLException(
201                     "QueryRunner requires a DataSource to be "
202                             + "invoked in this way, or a Connection should be passed in");
203         }
204         return this.getDataSource().getConnection();
205     }
206 
207     /**
208      * Fill the <code>PreparedStatement</code> replacement parameters with the
209      * given objects.
210      *
211      * @param stmt
212      *            PreparedStatement to fill
213      * @param params
214      *            Query replacement parameters; <code>null</code> is a valid
215      *            value to pass in.
216      * @throws SQLException
217      *             if a database access error occurs
218      */
219     public void fillStatement(PreparedStatement stmt, Object... params)
220             throws SQLException {
221 
222         // check the parameter count, if we can
223         ParameterMetaData pmd = null;
224         if (!pmdKnownBroken) {
225             pmd = stmt.getParameterMetaData();
226             int stmtCount = pmd.getParameterCount();
227             int paramsCount = params == null ? 0 : params.length;
228 
229             if (stmtCount != paramsCount) {
230                 throw new SQLException("Wrong number of parameters: expected "
231                         + stmtCount + ", was given " + paramsCount);
232             }
233         }
234 
235         // nothing to do here
236         if (params == null) {
237             return;
238         }
239 
240         for (int i = 0; i < params.length; i++) {
241             if (params[i] != null) {
242                 stmt.setObject(i + 1, params[i]);
243             } else {
244                 // VARCHAR works with many drivers regardless
245                 // of the actual column type. Oddly, NULL and
246                 // OTHER don't work with Oracle's drivers.
247                 int sqlType = Types.VARCHAR;
248                 if (!pmdKnownBroken) {
249                     try {
250                         /*
251                          * It's not possible for pmdKnownBroken to change from
252                          * true to false, (once true, always true) so pmd cannot
253                          * be null here.
254                          */
255                         sqlType = pmd.getParameterType(i + 1);
256                     } catch (SQLException e) {
257                         pmdKnownBroken = true;
258                     }
259                 }
260                 stmt.setNull(i + 1, sqlType);
261             }
262         }
263     }
264 
265     /**
266      * Fill the <code>PreparedStatement</code> replacement parameters with the
267      * given object's bean property values.
268      *
269      * @param stmt
270      *            PreparedStatement to fill
271      * @param bean
272      *            a JavaBean object
273      * @param properties
274      *            an ordered array of properties; this gives the order to insert
275      *            values in the statement
276      * @throws SQLException
277      *             if a database access error occurs
278      */
279     public void fillStatementWithBean(PreparedStatement stmt, Object bean,
280             PropertyDescriptor[] properties) throws SQLException {
281         Object[] params = new Object[properties.length];
282         for (int i = 0; i < properties.length; i++) {
283             PropertyDescriptor property = properties[i];
284             Object value = null;
285             Method method = property.getReadMethod();
286             if (method == null) {
287                 throw new RuntimeException("No read method for bean property "
288                         + bean.getClass() + " " + property.getName());
289             }
290             try {
291                 value = method.invoke(bean, new Object[0]);
292             } catch (InvocationTargetException e) {
293                 throw new RuntimeException("Couldn't invoke method: " + method,
294                         e);
295             } catch (IllegalArgumentException e) {
296                 throw new RuntimeException(
297                         "Couldn't invoke method with 0 arguments: " + method, e);
298             } catch (IllegalAccessException e) {
299                 throw new RuntimeException("Couldn't invoke method: " + method,
300                         e);
301             }
302             params[i] = value;
303         }
304         fillStatement(stmt, params);
305     }
306 
307     /**
308      * Fill the <code>PreparedStatement</code> replacement parameters with the
309      * given object's bean property values.
310      *
311      * @param stmt
312      *            PreparedStatement to fill
313      * @param bean
314      *            A JavaBean object
315      * @param propertyNames
316      *            An ordered array of property names (these should match the
317      *            getters/setters); this gives the order to insert values in the
318      *            statement
319      * @throws SQLException
320      *             If a database access error occurs
321      */
322     public void fillStatementWithBean(PreparedStatement stmt, Object bean,
323             String... propertyNames) throws SQLException {
324         PropertyDescriptor[] descriptors;
325         try {
326             descriptors = Introspector.getBeanInfo(bean.getClass())
327                     .getPropertyDescriptors();
328         } catch (IntrospectionException e) {
329             throw new RuntimeException("Couldn't introspect bean "
330                     + bean.getClass().toString(), e);
331         }
332         PropertyDescriptor[] sorted = new PropertyDescriptor[propertyNames.length];
333         for (int i = 0; i < propertyNames.length; i++) {
334             String propertyName = propertyNames[i];
335             if (propertyName == null) {
336                 throw new NullPointerException("propertyName can't be null: "
337                         + i);
338             }
339             boolean found = false;
340             for (int j = 0; j < descriptors.length; j++) {
341                 PropertyDescriptor descriptor = descriptors[j];
342                 if (propertyName.equals(descriptor.getName())) {
343                     sorted[i] = descriptor;
344                     found = true;
345                     break;
346                 }
347             }
348             if (!found) {
349                 throw new RuntimeException("Couldn't find bean property: "
350                         + bean.getClass() + " " + propertyName);
351             }
352         }
353         fillStatementWithBean(stmt, bean, sorted);
354     }
355 
356     /**
357      * Throws a new exception with a more informative error message.
358      *
359      * @param cause
360      *            The original exception that will be chained to the new
361      *            exception when it's rethrown.
362      *
363      * @param sql
364      *            The query that was executing when the exception happened.
365      *
366      * @param params
367      *            The query replacement parameters; <code>null</code> is a valid
368      *            value to pass in.
369      *
370      * @throws SQLException
371      *             if a database access error occurs
372      */
373     protected void rethrow(SQLException cause, String sql, Object... params)
374             throws SQLException {
375 
376         String causeMessage = cause.getMessage();
377         if (causeMessage == null) {
378             causeMessage = "";
379         }
380         StringBuffer msg = new StringBuffer(causeMessage);
381 
382         msg.append(" Query: ");
383         msg.append(sql);
384         msg.append(" Parameters: ");
385 
386         if (params == null) {
387             msg.append("[]");
388         } else {
389             msg.append(Arrays.deepToString(params));
390         }
391 
392         SQLException e = new SQLException(msg.toString(), cause.getSQLState(),
393                 cause.getErrorCode());
394         e.setNextException(cause);
395 
396         throw e;
397     }
398 
399     /**
400      * Wrap the <code>ResultSet</code> in a decorator before processing it. This
401      * implementation returns the <code>ResultSet</code> it is given without any
402      * decoration.
403      *
404      * <p>
405      * Often, the implementation of this method can be done in an anonymous
406      * inner class like this:
407      * </p>
408      *
409      * <pre>
410      * QueryRunner run = new QueryRunner() {
411      *     protected ResultSet wrap(ResultSet rs) {
412      *         return StringTrimmedResultSet.wrap(rs);
413      *     }
414      * };
415      * </pre>
416      *
417      * @param rs
418      *            The <code>ResultSet</code> to decorate; never
419      *            <code>null</code>.
420      * @return The <code>ResultSet</code> wrapped in some decorator.
421      */
422     protected ResultSet wrap(ResultSet rs) {
423         return rs;
424     }
425 
426     /**
427      * Close a <code>Connection</code>. This implementation avoids closing if
428      * null and does <strong>not</strong> suppress any exceptions. Subclasses
429      * can override to provide special handling like logging.
430      *
431      * @param conn
432      *            Connection to close
433      * @throws SQLException
434      *             if a database access error occurs
435      * @since DbUtils 1.1
436      */
437     protected void close(Connection conn) throws SQLException {
438         DbUtils.close(conn);
439     }
440 
441     /**
442      * Close a <code>Statement</code>. This implementation avoids closing if
443      * null and does <strong>not</strong> suppress any exceptions. Subclasses
444      * can override to provide special handling like logging.
445      *
446      * @param stmt
447      *            Statement to close
448      * @throws SQLException
449      *             if a database access error occurs
450      * @since DbUtils 1.1
451      */
452     protected void close(Statement stmt) throws SQLException {
453         DbUtils.close(stmt);
454     }
455 
456     /**
457      * Close a <code>ResultSet</code>. This implementation avoids closing if
458      * null and does <strong>not</strong> suppress any exceptions. Subclasses
459      * can override to provide special handling like logging.
460      *
461      * @param rs
462      *            ResultSet to close
463      * @throws SQLException
464      *             if a database access error occurs
465      * @since DbUtils 1.1
466      */
467     protected void close(ResultSet rs) throws SQLException {
468         DbUtils.close(rs);
469     }
470 
471 }