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 }