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      protected final DataSource ds;
52  
53      /**
54       * Default constructor, sets pmdKnownBroken to false and ds to null.
55       */
56      public AbstractQueryRunner() {
57          ds = null;
58      }
59  
60      /**
61       * Constructor to control the use of <code>ParameterMetaData</code>.
62       *
63       * @param pmdKnownBroken
64       *            Some drivers don't support
65       *            {@link ParameterMetaData#getParameterType(int) }; if
66       *            <code>pmdKnownBroken</code> is set to true, we won't even try
67       *            it; if false, we'll try it, and if it breaks, we'll remember
68       *            not to use it again.
69       */
70      public AbstractQueryRunner(boolean pmdKnownBroken) {
71          this.pmdKnownBroken = pmdKnownBroken;
72          ds = null;
73      }
74  
75      /**
76       * Constructor to provide a <code>DataSource</code>. Methods that do not
77       * take a <code>Connection</code> parameter will retrieve connections from
78       * this <code>DataSource</code>.
79       *
80       * @param ds
81       *            The <code>DataSource</code> to retrieve connections from.
82       */
83      public AbstractQueryRunner(DataSource ds) {
84          this.ds = ds;
85      }
86  
87      /**
88       * Constructor to provide a <code>DataSource</code> and control the use of
89       * <code>ParameterMetaData</code>. Methods that do not take a
90       * <code>Connection</code> parameter will retrieve connections from this
91       * <code>DataSource</code>.
92       *
93       * @param ds
94       *            The <code>DataSource</code> to retrieve connections from.
95       * @param pmdKnownBroken
96       *            Some drivers don't support
97       *            {@link ParameterMetaData#getParameterType(int) }; if
98       *            <code>pmdKnownBroken</code> is set to true, we won't even try
99       *            it; if false, we'll try it, and if it breaks, we'll remember
100      *            not to use it again.
101      */
102     public AbstractQueryRunner(DataSource ds, boolean pmdKnownBroken) {
103         this.pmdKnownBroken = pmdKnownBroken;
104         this.ds = ds;
105     }
106 
107     /**
108      * Returns the <code>DataSource</code> this runner is using.
109      * <code>QueryRunner</code> methods always call this method to get the
110      * <code>DataSource</code> so subclasses can provide specialized behavior.
111      *
112      * @return DataSource the runner is using
113      */
114     public DataSource getDataSource() {
115         return this.ds;
116     }
117 
118     /**
119      * Some drivers don't support
120      * {@link ParameterMetaData#getParameterType(int) }; if
121      * <code>pmdKnownBroken</code> is set to true, we won't even try it; if
122      * false, we'll try it, and if it breaks, we'll remember not to use it
123      * again.
124      *
125      * @return the flag to skip (or not)
126      *         {@link ParameterMetaData#getParameterType(int) }
127      * @since 1.4
128      */
129     public boolean isPmdKnownBroken() {
130         return pmdKnownBroken;
131     }
132 
133     /**
134      * Factory method that creates and initializes a
135      * <code>PreparedStatement</code> object for the given SQL.
136      * <code>QueryRunner</code> methods always call this method to prepare
137      * statements for them. Subclasses can override this method to provide
138      * special PreparedStatement configuration if needed. This implementation
139      * simply calls <code>conn.prepareStatement(sql)</code>.
140      *
141      * @param conn
142      *            The <code>Connection</code> used to create the
143      *            <code>PreparedStatement</code>
144      * @param sql
145      *            The SQL statement to prepare.
146      * @return An initialized <code>PreparedStatement</code>.
147      * @throws SQLException
148      *             if a database access error occurs
149      */
150     protected PreparedStatement prepareStatement(Connection conn, String sql)
151     throws SQLException {
152 
153         return conn.prepareStatement(sql);
154     }
155 
156     /**
157      * Factory method that creates and initializes a <code>Connection</code>
158      * object. <code>QueryRunner</code> methods always call this method to
159      * retrieve connections from its DataSource. Subclasses can override this
160      * method to provide special <code>Connection</code> configuration if
161      * needed. This implementation simply calls <code>ds.getConnection()</code>.
162      *
163      * @return An initialized <code>Connection</code>.
164      * @throws SQLException
165      *             if a database access error occurs
166      * @since DbUtils 1.1
167      */
168     protected Connection prepareConnection() throws SQLException {
169         if (this.getDataSource() == null) {
170             throw new SQLException(
171                     "QueryRunner requires a DataSource to be "
172                     + "invoked in this way, or a Connection should be passed in");
173         }
174         return this.getDataSource().getConnection();
175     }
176 
177     /**
178      * Fill the <code>PreparedStatement</code> replacement parameters with the
179      * given objects.
180      *
181      * @param stmt
182      *            PreparedStatement to fill
183      * @param params
184      *            Query replacement parameters; <code>null</code> is a valid
185      *            value to pass in.
186      * @throws SQLException
187      *             if a database access error occurs
188      */
189     public void fillStatement(PreparedStatement stmt, Object... params)
190     throws SQLException {
191 
192         // check the parameter count, if we can
193         ParameterMetaData pmd = null;
194         if (!pmdKnownBroken) {
195             pmd = stmt.getParameterMetaData();
196             int stmtCount = pmd.getParameterCount();
197             int paramsCount = params == null ? 0 : params.length;
198 
199             if (stmtCount != paramsCount) {
200                 throw new SQLException("Wrong number of parameters: expected "
201                         + stmtCount + ", was given " + paramsCount);
202             }
203         }
204 
205         // nothing to do here
206         if (params == null) {
207             return;
208         }
209 
210         for (int i = 0; i < params.length; i++) {
211             if (params[i] != null) {
212                 stmt.setObject(i + 1, params[i]);
213             } else {
214                 // VARCHAR works with many drivers regardless
215                 // of the actual column type. Oddly, NULL and
216                 // OTHER don't work with Oracle's drivers.
217                 int sqlType = Types.VARCHAR;
218                 if (!pmdKnownBroken) {
219                     try {
220                         /*
221                          * It's not possible for pmdKnownBroken to change from
222                          * true to false, (once true, always true) so pmd cannot
223                          * be null here.
224                          */
225                         sqlType = pmd.getParameterType(i + 1);
226                     } catch (SQLException e) {
227                         pmdKnownBroken = true;
228                     }
229                 }
230                 stmt.setNull(i + 1, sqlType);
231             }
232         }
233     }
234 
235     /**
236      * Fill the <code>PreparedStatement</code> replacement parameters with the
237      * given object's bean property values.
238      *
239      * @param stmt
240      *            PreparedStatement to fill
241      * @param bean
242      *            a JavaBean object
243      * @param properties
244      *            an ordered array of properties; this gives the order to insert
245      *            values in the statement
246      * @throws SQLException
247      *             if a database access error occurs
248      */
249     public void fillStatementWithBean(PreparedStatement stmt, Object bean,
250             PropertyDescriptor[] properties) throws SQLException {
251         Object[] params = new Object[properties.length];
252         for (int i = 0; i < properties.length; i++) {
253             PropertyDescriptor property = properties[i];
254             Object value = null;
255             Method method = property.getReadMethod();
256             if (method == null) {
257                 throw new RuntimeException("No read method for bean property "
258                         + bean.getClass() + " " + property.getName());
259             }
260             try {
261                 value = method.invoke(bean, new Object[0]);
262             } catch (InvocationTargetException e) {
263                 throw new RuntimeException("Couldn't invoke method: " + method,
264                         e);
265             } catch (IllegalArgumentException e) {
266                 throw new RuntimeException(
267                         "Couldn't invoke method with 0 arguments: " + method, e);
268             } catch (IllegalAccessException e) {
269                 throw new RuntimeException("Couldn't invoke method: " + method,
270                         e);
271             }
272             params[i] = value;
273         }
274         fillStatement(stmt, params);
275     }
276 
277     /**
278      * Fill the <code>PreparedStatement</code> replacement parameters with the
279      * given object's bean property values.
280      *
281      * @param stmt
282      *            PreparedStatement to fill
283      * @param bean
284      *            A JavaBean object
285      * @param propertyNames
286      *            An ordered array of property names (these should match the
287      *            getters/setters); this gives the order to insert values in the
288      *            statement
289      * @throws SQLException
290      *             If a database access error occurs
291      */
292     public void fillStatementWithBean(PreparedStatement stmt, Object bean,
293             String... propertyNames) throws SQLException {
294         PropertyDescriptor[] descriptors;
295         try {
296             descriptors = Introspector.getBeanInfo(bean.getClass())
297             .getPropertyDescriptors();
298         } catch (IntrospectionException e) {
299             throw new RuntimeException("Couldn't introspect bean "
300                     + bean.getClass().toString(), e);
301         }
302         PropertyDescriptor[] sorted = new PropertyDescriptor[propertyNames.length];
303         for (int i = 0; i < propertyNames.length; i++) {
304             String propertyName = propertyNames[i];
305             if (propertyName == null) {
306                 throw new NullPointerException("propertyName can't be null: "
307                         + i);
308             }
309             boolean found = false;
310             for (int j = 0; j < descriptors.length; j++) {
311                 PropertyDescriptor descriptor = descriptors[j];
312                 if (propertyName.equals(descriptor.getName())) {
313                     sorted[i] = descriptor;
314                     found = true;
315                     break;
316                 }
317             }
318             if (!found) {
319                 throw new RuntimeException("Couldn't find bean property: "
320                         + bean.getClass() + " " + propertyName);
321             }
322         }
323         fillStatementWithBean(stmt, bean, sorted);
324     }
325 
326     /**
327      * Throws a new exception with a more informative error message.
328      *
329      * @param cause
330      *            The original exception that will be chained to the new
331      *            exception when it's rethrown.
332      *
333      * @param sql
334      *            The query that was executing when the exception happened.
335      *
336      * @param params
337      *            The query replacement parameters; <code>null</code> is a valid
338      *            value to pass in.
339      *
340      * @throws SQLException
341      *             if a database access error occurs
342      */
343     protected void rethrow(SQLException cause, String sql, Object... params)
344     throws SQLException {
345 
346         String causeMessage = cause.getMessage();
347         if (causeMessage == null) {
348             causeMessage = "";
349         }
350         StringBuffer msg = new StringBuffer(causeMessage);
351 
352         msg.append(" Query: ");
353         msg.append(sql);
354         msg.append(" Parameters: ");
355 
356         if (params == null) {
357             msg.append("[]");
358         } else {
359             msg.append(Arrays.deepToString(params));
360         }
361 
362         SQLException e = new SQLException(msg.toString(), cause.getSQLState(),
363                 cause.getErrorCode());
364         e.setNextException(cause);
365 
366         throw e;
367     }
368 
369     /**
370      * Wrap the <code>ResultSet</code> in a decorator before processing it. This
371      * implementation returns the <code>ResultSet</code> it is given without any
372      * decoration.
373      *
374      * <p>
375      * Often, the implementation of this method can be done in an anonymous
376      * inner class like this:
377      * </p>
378      *
379      * <pre>
380      * QueryRunner run = new QueryRunner() {
381      *     protected ResultSet wrap(ResultSet rs) {
382      *         return StringTrimmedResultSet.wrap(rs);
383      *     }
384      * };
385      * </pre>
386      *
387      * @param rs
388      *            The <code>ResultSet</code> to decorate; never
389      *            <code>null</code>.
390      * @return The <code>ResultSet</code> wrapped in some decorator.
391      */
392     protected ResultSet wrap(ResultSet rs) {
393         return rs;
394     }
395 
396     /**
397      * Close a <code>Connection</code>. This implementation avoids closing if
398      * null and does <strong>not</strong> suppress any exceptions. Subclasses
399      * can override to provide special handling like logging.
400      *
401      * @param conn
402      *            Connection to close
403      * @throws SQLException
404      *             if a database access error occurs
405      * @since DbUtils 1.1
406      */
407     protected void close(Connection conn) throws SQLException {
408         DbUtils.close(conn);
409     }
410 
411     /**
412      * Close a <code>Statement</code>. This implementation avoids closing if
413      * null and does <strong>not</strong> suppress any exceptions. Subclasses
414      * can override to provide special handling like logging.
415      *
416      * @param stmt
417      *            Statement to close
418      * @throws SQLException
419      *             if a database access error occurs
420      * @since DbUtils 1.1
421      */
422     protected void close(Statement stmt) throws SQLException {
423         DbUtils.close(stmt);
424     }
425 
426     /**
427      * Close a <code>ResultSet</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 rs
432      *            ResultSet to close
433      * @throws SQLException
434      *             if a database access error occurs
435      * @since DbUtils 1.1
436      */
437     protected void close(ResultSet rs) throws SQLException {
438         DbUtils.close(rs);
439     }
440 
441 }