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.ArrayList;
23  import java.util.List;
24  import java.util.Objects;
25  
26  import org.apache.commons.beanutils2.BasicDynaBean;
27  import org.apache.commons.beanutils2.DynaBean;
28  import org.apache.commons.beanutils2.DynaClass;
29  import org.apache.commons.beanutils2.DynaProperty;
30  
31  /**
32   * <p>
33   * Implements {@link DynaClass} to create an in-memory collection of {@link DynaBean}s representing the results of an SQL query. Once the {@link DynaClass}
34   * instance has been created, the JDBC {@code ResultSet} and {@code Statement} on which it is based can be closed, and the underlying {@code Connection} can be
35   * returned to its connection pool (if you are using one).
36   * </p>
37   *
38   * <p>
39   * The normal usage pattern is something like:
40   * </p>
41   *
42   * <pre>
43   *   Connection conn = ...;  // Acquire connection from pool
44   *   Statement stmt = conn.createStatement();
45   *   ResultSet rs = stmt.executeQuery("SELECT ...");
46   *   RowSetDynaClass rsdc = new RowSetDynaClass(rs);
47   *   rs.close();
48   *   stmt.close();
49   *   ...;                    // Return connection to pool
50   *   List rows = rsdc.getRows();
51   *   ...;                   // Process the rows as desired
52   * </pre>
53   *
54   * <p>
55   * Each column in the result set will be represented as a {@link DynaBean} property of the corresponding name (optionally forced to lower case for portability).
56   * There will be one {@link DynaBean} in the {@code List</code> returned by <code>getRows()} for each row in the original {@code ResultSet}.
57   * </p>
58   *
59   * <p>
60   * In general, instances of {@link RowSetDynaClass} can be serialized and deserialized, which will automatically include the list of {@link DynaBean}s
61   * representing the data content. The only exception to this rule would be when the underlying property values that were copied from the {@code ResultSet}
62   * originally cannot themselves be serialized. Therefore, a {@link RowSetDynaClass} makes a very convenient mechanism for transporting data sets to remote
63   * Java-based application components.
64   * </p>
65   */
66  public class RowSetDynaClass extends AbstractJdbcDynaClass {
67  
68      /**
69       * <p>
70       * Limits the size of the returned list. The call to {@code getRows()} will return at most limit number of rows. If less than or equal to 0, does not limit
71       * the size of the result.
72       */
73      protected int limit = -1;
74  
75      /**
76       * <p>
77       * The list of {@link DynaBean}s representing the contents of the original {@code ResultSet} on which this {@link RowSetDynaClass} was based.
78       * </p>
79       */
80      protected List<DynaBean> rows = new ArrayList<>();
81  
82      /**
83       * <p>
84       * Constructs a new {@link RowSetDynaClass} for the specified {@code ResultSet}. The property names corresponding to column names in the result set will be
85       * lower cased.
86       * </p>
87       *
88       * @param resultSet The result set to be wrapped
89       * @throws NullPointerException if {@code resultSet} is {@code null}
90       * @throws SQLException         if the metadata for this result set cannot be introspected
91       */
92      public RowSetDynaClass(final ResultSet resultSet) throws SQLException {
93          this(resultSet, true, -1);
94      }
95  
96      /**
97       * <p>
98       * Constructs a new {@link RowSetDynaClass} for the specified {@code ResultSet}. The property names corresponding to the column names in the result set will
99       * be lower cased or not, depending on the specified {@code lowerCase} value.
100      * </p>
101      *
102      * If {@code limit</code> is not less than 0, max <code>limit} number of rows will be copied into the resultset.
103      *
104      *
105      * @param resultSet The result set to be wrapped
106      * @param lowerCase Should property names be lower cased?
107      * @throws NullPointerException if {@code resultSet} is {@code null}
108      * @throws SQLException         if the metadata for this result set cannot be introspected
109      */
110     public RowSetDynaClass(final ResultSet resultSet, final boolean lowerCase) throws SQLException {
111         this(resultSet, lowerCase, -1);
112     }
113 
114     /**
115      * <p>
116      * Constructs a new {@link RowSetDynaClass} for the specified {@code ResultSet}. The property names corresponding to the column names in the result set will
117      * be lower cased or not, depending on the specified {@code lowerCase} value.
118      * </p>
119      *
120      * <p>
121      * <strong>WARNING</strong> - If you specify {@code false} for {@code lowerCase}, the returned property names will exactly match the column names returned
122      * by your JDBC driver. Because different drivers might return column names in different cases, the property names seen by your application will vary
123      * depending on which JDBC driver you are using.
124      * </p>
125      *
126      * @param resultSet      The result set to be wrapped
127      * @param lowerCase      Should property names be lower cased?
128      * @param useColumnLabel true if the column label should be used, otherwise false
129      * @throws NullPointerException if {@code resultSet} is {@code null}
130      * @throws SQLException         if the metadata for this result set cannot be introspected
131      * @since 1.8.3
132      */
133     public RowSetDynaClass(final ResultSet resultSet, final boolean lowerCase, final boolean useColumnLabel) throws SQLException {
134         this(resultSet, lowerCase, -1, useColumnLabel);
135     }
136 
137     /**
138      * <p>
139      * Constructs a new {@link RowSetDynaClass} for the specified {@code ResultSet}. The property names corresponding to the column names in the result set will
140      * be lower cased or not, depending on the specified {@code lowerCase} value.
141      * </p>
142      *
143      * <p>
144      * <strong>WARNING</strong> - If you specify {@code false} for {@code lowerCase}, the returned property names will exactly match the column names returned
145      * by your JDBC driver. Because different drivers might return column names in different cases, the property names seen by your application will vary
146      * depending on which JDBC driver you are using.
147      * </p>
148      *
149      * @param resultSet The result set to be wrapped
150      * @param lowerCase Should property names be lower cased?
151      * @param limit     Maximum limit for the {@code List} of {@link DynaBean}
152      * @throws NullPointerException if {@code resultSet} is {@code null}
153      * @throws SQLException         if the metadata for this result set cannot be introspected
154      */
155     public RowSetDynaClass(final ResultSet resultSet, final boolean lowerCase, final int limit) throws SQLException {
156         this(resultSet, lowerCase, limit, false);
157     }
158 
159     /**
160      * <p>
161      * Constructs a new {@link RowSetDynaClass} for the specified {@code ResultSet}. The property names corresponding to the column names in the result set will
162      * be lower cased or not, depending on the specified {@code lowerCase} value.
163      * </p>
164      *
165      * <p>
166      * <strong>WARNING</strong> - If you specify {@code false} for {@code lowerCase}, the returned property names will exactly match the column names returned
167      * by your JDBC driver. Because different drivers might return column names in different cases, the property names seen by your application will vary
168      * depending on which JDBC driver you are using.
169      * </p>
170      *
171      * @param resultSet      The result set to be wrapped
172      * @param lowerCase      Should property names be lower cased?
173      * @param limit          Maximum limit for the {@code List} of {@link DynaBean}
174      * @param useColumnLabel true if the column label should be used, otherwise false
175      * @throws NullPointerException if {@code resultSet} is {@code null}
176      * @throws SQLException         if the metadata for this result set cannot be introspected
177      * @since 1.8.3
178      */
179     @SuppressWarnings("resource") // resultSet is not allocated here
180     public RowSetDynaClass(final ResultSet resultSet, final boolean lowerCase, final int limit, final boolean useColumnLabel) throws SQLException {
181         Objects.requireNonNull(resultSet, "resultSet");
182         this.lowerCase = lowerCase;
183         this.limit = limit;
184         setUseColumnLabel(useColumnLabel);
185         introspect(resultSet);
186         copy(resultSet);
187     }
188 
189     /**
190      * <p>
191      * Constructs a new {@link RowSetDynaClass} for the specified {@code ResultSet}. The property names corresponding to column names in the result set will be
192      * lower cased.
193      * </p>
194      *
195      * If {@code limit</code> is not less than 0, max <code>limit} number of rows will be copied into the list.
196      *
197      * @param resultSet The result set to be wrapped
198      * @param limit     The maximum for the size of the result.
199      * @throws NullPointerException if {@code resultSet} is {@code null}
200      * @throws SQLException         if the metadata for this result set cannot be introspected
201      */
202     public RowSetDynaClass(final ResultSet resultSet, final int limit) throws SQLException {
203         this(resultSet, true, limit);
204     }
205 
206     /**
207      * <p>
208      * Copy the column values for each row in the specified {@code ResultSet} into a newly created {@link DynaBean}, and add this bean to the list of
209      * {@link DynaBean}s that will later by returned by a call to {@code getRows()}.
210      * </p>
211      *
212      * @param resultSet The {@code ResultSet} whose data is to be copied
213      * @throws SQLException if an error is encountered copying the data
214      */
215     protected void copy(final ResultSet resultSet) throws SQLException {
216         int cnt = 0;
217         while (resultSet.next() && (limit < 0 || cnt++ < limit)) {
218             final DynaBean bean = createDynaBean();
219             for (final DynaProperty property : properties) {
220                 final String name = property.getName();
221                 final Object value = getObject(resultSet, name);
222                 bean.set(name, value);
223             }
224             rows.add(bean);
225         }
226     }
227 
228     /**
229      * <p>
230      * Create and return a new {@link DynaBean} instance to be used for representing a row in the underlying result set.
231      * </p>
232      *
233      * @return A new {@code DynaBean} instance
234      */
235     protected DynaBean createDynaBean() {
236         return new BasicDynaBean(this);
237     }
238 
239     /**
240      * <p>
241      * Gets a {@code List} containing the {@link DynaBean}s that represent the contents of each {@code Row} from the {@code ResultSet} that was the basis of
242      * this {@link RowSetDynaClass} instance. These {@link DynaBean}s are disconnected from the database itself, so there is no problem with modifying the
243      * contents of the list, or the values of the properties of these {@link DynaBean}s. However, it is the application's responsibility to persist any such
244      * changes back to the database, if it so desires.
245      * </p>
246      *
247      * @return A {@code List} of {@link DynaBean} instances
248      */
249     public List<DynaBean> getRows() {
250         return this.rows;
251     }
252 
253 }