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 * https://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 }