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      */
179     protected PreparedStatement prepareStatement(Connection conn, String sql, int returnedKeys)
180             throws SQLException {
181 
182         return conn.prepareStatement(sql, returnedKeys);
183     }
184 
185     /**
186      * Factory method that creates and initializes a <code>Connection</code>
187      * object. <code>QueryRunner</code> methods always call this method to
188      * retrieve connections from its DataSource. Subclasses can override this
189      * method to provide special <code>Connection</code> configuration if
190      * needed. This implementation simply calls <code>ds.getConnection()</code>.
191      *
192      * @return An initialized <code>Connection</code>.
193      * @throws SQLException
194      *             if a database access error occurs
195      * @since DbUtils 1.1
196      */
197     protected Connection prepareConnection() throws SQLException {
198         if (this.getDataSource() == null) {
199             throw new SQLException(
200                     "QueryRunner requires a DataSource to be "
201                             + "invoked in this way, or a Connection should be passed in");
202         }
203         return this.getDataSource().getConnection();
204     }
205 
206     /**
207      * Fill the <code>PreparedStatement</code> replacement parameters with the
208      * given objects.
209      *
210      * @param stmt
211      *            PreparedStatement to fill
212      * @param params
213      *            Query replacement parameters; <code>null</code> is a valid
214      *            value to pass in.
215      * @throws SQLException
216      *             if a database access error occurs
217      */
218     public void fillStatement(PreparedStatement stmt, Object... params)
219             throws SQLException {
220 
221         // check the parameter count, if we can
222         ParameterMetaData pmd = null;
223         if (!pmdKnownBroken) {
224             pmd = stmt.getParameterMetaData();
225             int stmtCount = pmd.getParameterCount();
226             int paramsCount = params == null ? 0 : params.length;
227 
228             if (stmtCount != paramsCount) {
229                 throw new SQLException("Wrong number of parameters: expected "
230                         + stmtCount + ", was given " + paramsCount);
231             }
232         }
233 
234         // nothing to do here
235         if (params == null) {
236             return;
237         }
238 
239         for (int i = 0; i < params.length; i++) {
240             if (params[i] != null) {
241                 stmt.setObject(i + 1, params[i]);
242             } else {
243                 // VARCHAR works with many drivers regardless
244                 // of the actual column type. Oddly, NULL and
245                 // OTHER don't work with Oracle's drivers.
246                 int sqlType = Types.VARCHAR;
247                 if (!pmdKnownBroken) {
248                     try {
249                         /*
250                          * It's not possible for pmdKnownBroken to change from
251                          * true to false, (once true, always true) so pmd cannot
252                          * be null here.
253                          */
254                         sqlType = pmd.getParameterType(i + 1);
255                     } catch (SQLException e) {
256                         pmdKnownBroken = true;
257                     }
258                 }
259                 stmt.setNull(i + 1, sqlType);
260             }
261         }
262     }
263 
264     /**
265      * Fill the <code>PreparedStatement</code> replacement parameters with the
266      * given object's bean property values.
267      *
268      * @param stmt
269      *            PreparedStatement to fill
270      * @param bean
271      *            a JavaBean object
272      * @param properties
273      *            an ordered array of properties; this gives the order to insert
274      *            values in the statement
275      * @throws SQLException
276      *             if a database access error occurs
277      */
278     public void fillStatementWithBean(PreparedStatement stmt, Object bean,
279             PropertyDescriptor[] properties) throws SQLException {
280         Object[] params = new Object[properties.length];
281         for (int i = 0; i < properties.length; i++) {
282             PropertyDescriptor property = properties[i];
283             Object value = null;
284             Method method = property.getReadMethod();
285             if (method == null) {
286                 throw new RuntimeException("No read method for bean property "
287                         + bean.getClass() + " " + property.getName());
288             }
289             try {
290                 value = method.invoke(bean, new Object[0]);
291             } catch (InvocationTargetException e) {
292                 throw new RuntimeException("Couldn't invoke method: " + method,
293                         e);
294             } catch (IllegalArgumentException e) {
295                 throw new RuntimeException(
296                         "Couldn't invoke method with 0 arguments: " + method, e);
297             } catch (IllegalAccessException e) {
298                 throw new RuntimeException("Couldn't invoke method: " + method,
299                         e);
300             }
301             params[i] = value;
302         }
303         fillStatement(stmt, params);
304     }
305 
306     /**
307      * Fill the <code>PreparedStatement</code> replacement parameters with the
308      * given object's bean property values.
309      *
310      * @param stmt
311      *            PreparedStatement to fill
312      * @param bean
313      *            A JavaBean object
314      * @param propertyNames
315      *            An ordered array of property names (these should match the
316      *            getters/setters); this gives the order to insert values in the
317      *            statement
318      * @throws SQLException
319      *             If a database access error occurs
320      */
321     public void fillStatementWithBean(PreparedStatement stmt, Object bean,
322             String... propertyNames) throws SQLException {
323         PropertyDescriptor[] descriptors;
324         try {
325             descriptors = Introspector.getBeanInfo(bean.getClass())
326                     .getPropertyDescriptors();
327         } catch (IntrospectionException e) {
328             throw new RuntimeException("Couldn't introspect bean "
329                     + bean.getClass().toString(), e);
330         }
331         PropertyDescriptor[] sorted = new PropertyDescriptor[propertyNames.length];
332         for (int i = 0; i < propertyNames.length; i++) {
333             String propertyName = propertyNames[i];
334             if (propertyName == null) {
335                 throw new NullPointerException("propertyName can't be null: "
336                         + i);
337             }
338             boolean found = false;
339             for (int j = 0; j < descriptors.length; j++) {
340                 PropertyDescriptor descriptor = descriptors[j];
341                 if (propertyName.equals(descriptor.getName())) {
342                     sorted[i] = descriptor;
343                     found = true;
344                     break;
345                 }
346             }
347             if (!found) {
348                 throw new RuntimeException("Couldn't find bean property: "
349                         + bean.getClass() + " " + propertyName);
350             }
351         }
352         fillStatementWithBean(stmt, bean, sorted);
353     }
354 
355     /**
356      * Throws a new exception with a more informative error message.
357      *
358      * @param cause
359      *            The original exception that will be chained to the new
360      *            exception when it's rethrown.
361      *
362      * @param sql
363      *            The query that was executing when the exception happened.
364      *
365      * @param params
366      *            The query replacement parameters; <code>null</code> is a valid
367      *            value to pass in.
368      *
369      * @throws SQLException
370      *             if a database access error occurs
371      */
372     protected void rethrow(SQLException cause, String sql, Object... params)
373             throws SQLException {
374 
375         String causeMessage = cause.getMessage();
376         if (causeMessage == null) {
377             causeMessage = "";
378         }
379         StringBuffer msg = new StringBuffer(causeMessage);
380 
381         msg.append(" Query: ");
382         msg.append(sql);
383         msg.append(" Parameters: ");
384 
385         if (params == null) {
386             msg.append("[]");
387         } else {
388             msg.append(Arrays.deepToString(params));
389         }
390 
391         SQLException e = new SQLException(msg.toString(), cause.getSQLState(),
392                 cause.getErrorCode());
393         e.setNextException(cause);
394 
395         throw e;
396     }
397 
398     /**
399      * Wrap the <code>ResultSet</code> in a decorator before processing it. This
400      * implementation returns the <code>ResultSet</code> it is given without any
401      * decoration.
402      *
403      * <p>
404      * Often, the implementation of this method can be done in an anonymous
405      * inner class like this:
406      * </p>
407      *
408      * <pre>
409      * QueryRunner run = new QueryRunner() {
410      *     protected ResultSet wrap(ResultSet rs) {
411      *         return StringTrimmedResultSet.wrap(rs);
412      *     }
413      * };
414      * </pre>
415      *
416      * @param rs
417      *            The <code>ResultSet</code> to decorate; never
418      *            <code>null</code>.
419      * @return The <code>ResultSet</code> wrapped in some decorator.
420      */
421     protected ResultSet wrap(ResultSet rs) {
422         return rs;
423     }
424 
425     /**
426      * Close a <code>Connection</code>. This implementation avoids closing if
427      * null and does <strong>not</strong> suppress any exceptions. Subclasses
428      * can override to provide special handling like logging.
429      *
430      * @param conn
431      *            Connection to close
432      * @throws SQLException
433      *             if a database access error occurs
434      * @since DbUtils 1.1
435      */
436     protected void close(Connection conn) throws SQLException {
437         DbUtils.close(conn);
438     }
439 
440     /**
441      * Close a <code>Statement</code>. This implementation avoids closing if
442      * null and does <strong>not</strong> suppress any exceptions. Subclasses
443      * can override to provide special handling like logging.
444      *
445      * @param stmt
446      *            Statement to close
447      * @throws SQLException
448      *             if a database access error occurs
449      * @since DbUtils 1.1
450      */
451     protected void close(Statement stmt) throws SQLException {
452         DbUtils.close(stmt);
453     }
454 
455     /**
456      * Close a <code>ResultSet</code>. This implementation avoids closing if
457      * null and does <strong>not</strong> suppress any exceptions. Subclasses
458      * can override to provide special handling like logging.
459      *
460      * @param rs
461      *            ResultSet to close
462      * @throws SQLException
463      *             if a database access error occurs
464      * @since DbUtils 1.1
465      */
466     protected void close(ResultSet rs) throws SQLException {
467         DbUtils.close(rs);
468     }
469 
470 }