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  
18  package org.apache.commons.beanutils2.sql;
19  
20  import java.sql.ResultSet;
21  import java.sql.SQLException;
22  import java.util.Iterator;
23  import java.util.Objects;
24  
25  import org.apache.commons.beanutils2.DynaBean;
26  import org.apache.commons.beanutils2.DynaClass;
27  
28  /**
29   * <p>
30   * Implements {@link DynaClass} for DynaBeans that wrap the {@code java.sql.Row</code> objects of a <code>java.sql.ResultSet}. The normal usage pattern is
31   * something like:
32   * </p>
33   *
34   * <pre>
35   *   ResultSet rs = ...;
36   *   ResultSetDynaClass rsdc = new ResultSetDynaClass(rs);
37   *   Iterator rows = rsdc.iterator();
38   *   while (rows.hasNext())  {
39   *     DynaBean row = (DynaBean) rows.next();
40   *     ... process this row ...
41   *   }
42   *   rs.close();
43   * </pre>
44   *
45   * <p>
46   * Each column in the result set will be represented as a DynaBean property of the corresponding name (optionally forced to lower case for portability).
47   * </p>
48   *
49   * <p>
50   * <strong>WARNING</strong> - Any {@link DynaBean} instance returned by this class, or from the {@code Iterator} returned by the {@code iterator()} method, is
51   * directly linked to the row that the underlying result set is currently positioned at. This has the following implications:
52   * </p>
53   * <ul>
54   * <li>Once you retrieve a different {@link DynaBean} instance, you should no longer use any previous instance.</li>
55   * <li>Changing the position of the underlying result set will change the data that the {@link DynaBean} references.</li>
56   * <li>Once the underlying result set is closed, the {@link DynaBean} instance may no longer be used.</li>
57   * </ul>
58   *
59   * <p>
60   * Any database data that you wish to utilize outside the context of the current row of an open result set must be copied. For example, you could use the
61   * following code to create standalone copies of the information in a result set:
62   * </p>
63   *
64   * <pre>
65   *   List results = new ArrayList(); // To hold copied list
66   *   ResultSetDynaClass rsdc = ...;
67   *   DynaProperty[] properties = rsdc.getDynaProperties();
68   *   BasicDynaClass bdc =
69   *     new BasicDynaClass("foo", BasicDynaBean.class,
70   *                        rsdc.getDynaProperties());
71   *   Iterator rows = rsdc.iterator();
72   *   while (rows.hasNext()) {
73   *     DynaBean oldRow = (DynaBean) rows.next();
74   *     DynaBean newRow = bdc.newInstance();
75   *     PropertyUtils.copyProperties(newRow, oldRow);
76   *     results.add(newRow);
77   *   }
78   * </pre>
79   */
80  public class ResultSetDynaClass extends AbstractJdbcDynaClass {
81  
82      private static final long serialVersionUID = 1L;
83  
84      /**
85       * <p>
86       * The {@code ResultSet} we are wrapping.
87       * </p>
88       */
89      protected ResultSet resultSet;
90  
91      /**
92       * <p>
93       * Constructs a new ResultSetDynaClass for the specified {@code ResultSet}. The property names corresponding to column names in the result set will be lower
94       * cased.
95       * </p>
96       *
97       * @param resultSet The result set to be wrapped
98       * @throws NullPointerException if {@code resultSet} is {@code null}
99       * @throws SQLException         if the metadata for this result set cannot be introspected
100      */
101     public ResultSetDynaClass(final ResultSet resultSet) throws SQLException {
102         this(resultSet, true);
103     }
104 
105     /**
106      * <p>
107      * Constructs a new ResultSetDynaClass for the specified {@code ResultSet}. The property names corresponding to the column names in the result set will be
108      * lower cased or not, depending on the specified {@code lowerCase} value.
109      * </p>
110      *
111      * <p>
112      * <strong>WARNING</strong> - If you specify {@code false} for {@code lowerCase}, the returned property names will exactly match the column names returned
113      * by your JDBC driver. Because different drivers might return column names in different cases, the property names seen by your application will vary
114      * depending on which JDBC driver you are using.
115      * </p>
116      *
117      * @param resultSet The result set to be wrapped
118      * @param lowerCase Should property names be lower cased?
119      * @throws NullPointerException if {@code resultSet} is {@code null}
120      * @throws SQLException         if the metadata for this result set cannot be introspected
121      */
122     public ResultSetDynaClass(final ResultSet resultSet, final boolean lowerCase) throws SQLException {
123         this(resultSet, lowerCase, false);
124     }
125 
126     /**
127      * <p>
128      * Constructs a new ResultSetDynaClass for the specified {@code ResultSet}. The property names corresponding to the column names in the result set will be
129      * lower cased or not, depending on the specified {@code lowerCase} value.
130      * </p>
131      *
132      * <p>
133      * <strong>WARNING</strong> - If you specify {@code false} for {@code lowerCase}, the returned property names will exactly match the column names returned
134      * by your JDBC driver. Because different drivers might return column names in different cases, the property names seen by your application will vary
135      * depending on which JDBC driver you are using.
136      * </p>
137      *
138      * @param resultSet      The result set to be wrapped
139      * @param lowerCase      Should property names be lower cased?
140      * @param useColumnLabel true if the column label should be used, otherwise false
141      * @throws NullPointerException if {@code resultSet} is {@code null}
142      * @throws SQLException         if the metadata for this result set cannot be introspected
143      * @since 1.8.3
144      */
145     public ResultSetDynaClass(final ResultSet resultSet, final boolean lowerCase, final boolean useColumnLabel) throws SQLException {
146         this.resultSet = Objects.requireNonNull(resultSet, "resultSet");
147         this.lowerCase = lowerCase;
148         setUseColumnLabel(useColumnLabel);
149         introspect(resultSet);
150     }
151 
152     /**
153      * Gets a value from the {@link ResultSet} for the specified property name.
154      *
155      * @param name The property name
156      * @return The value
157      * @throws SQLException if an error occurs
158      * @since 1.8.0
159      */
160     @SuppressWarnings("resource") // getResultSet() does not allocate.
161     public Object getObjectFromResultSet(final String name) throws SQLException {
162         return getObject(getResultSet(), name);
163     }
164 
165     /**
166      * <p>
167      * Gets the result set we are wrapping.
168      * </p>
169      */
170     ResultSet getResultSet() {
171         return this.resultSet;
172     }
173 
174     /**
175      * <p>
176      * Return an {@code Iterator} of {@link DynaBean} instances for each row of the wrapped {@code ResultSet}, in "forward" order. Unless the underlying result
177      * set supports scrolling, this method should be called only once.
178      * </p>
179      *
180      * @return An {@code Iterator} of {@link DynaBean} instances
181      */
182     public Iterator<DynaBean> iterator() {
183         return new ResultSetIterator(this);
184     }
185 
186     /**
187      * <p>
188      * Loads the class of the given name which by default uses the class loader used to load this library. Derivations of this class could implement alternative
189      * class loading policies such as using custom ClassLoader or using the Threads's context class loader etc.
190      * </p>
191      *
192      * @param className The name of the class to load
193      * @return The loaded class
194      * @throws SQLException if the class cannot be loaded
195      */
196     @Override
197     protected Class<?> loadClass(final String className) throws SQLException {
198         try {
199             return getClass().getClassLoader().loadClass(className);
200         } catch (final Exception e) {
201             throw new SQLException("Cannot load column class '" + className + "': " + e);
202         }
203     }
204 }