001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.beanutils2.sql; 019 020import java.sql.ResultSet; 021import java.sql.SQLException; 022import java.util.ArrayList; 023import java.util.List; 024import java.util.Objects; 025 026import org.apache.commons.beanutils2.BasicDynaBean; 027import org.apache.commons.beanutils2.DynaBean; 028import org.apache.commons.beanutils2.DynaClass; 029import org.apache.commons.beanutils2.DynaProperty; 030 031/** 032 * <p> 033 * Implements {@link DynaClass} to create an in-memory collection of {@link DynaBean}s representing the results of an SQL query. Once the {@link DynaClass} 034 * 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 035 * returned to its connection pool (if you are using one). 036 * </p> 037 * 038 * <p> 039 * The normal usage pattern is something like: 040 * </p> 041 * 042 * <pre> 043 * Connection conn = ...; // Acquire connection from pool 044 * Statement stmt = conn.createStatement(); 045 * ResultSet rs = stmt.executeQuery("SELECT ..."); 046 * RowSetDynaClass rsdc = new RowSetDynaClass(rs); 047 * rs.close(); 048 * stmt.close(); 049 * ...; // Return connection to pool 050 * List rows = rsdc.getRows(); 051 * ...; // Process the rows as desired 052 * </pre> 053 * 054 * <p> 055 * 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). 056 * There will be one {@link DynaBean} in the {@code List</code> returned by <code>getRows()} for each row in the original {@code ResultSet}. 057 * </p> 058 * 059 * <p> 060 * In general, instances of {@link RowSetDynaClass} can be serialized and deserialized, which will automatically include the list of {@link DynaBean}s 061 * representing the data content. The only exception to this rule would be when the underlying property values that were copied from the {@code ResultSet} 062 * originally cannot themselves be serialized. Therefore, a {@link RowSetDynaClass} makes a very convenient mechanism for transporting data sets to remote 063 * Java-based application components. 064 * </p> 065 */ 066public class RowSetDynaClass extends AbstractJdbcDynaClass { 067 068 /** 069 * <p> 070 * 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 071 * the size of the result. 072 */ 073 protected int limit = -1; 074 075 /** 076 * <p> 077 * The list of {@link DynaBean}s representing the contents of the original {@code ResultSet} on which this {@link RowSetDynaClass} was based. 078 * </p> 079 */ 080 protected List<DynaBean> rows = new ArrayList<>(); 081 082 /** 083 * <p> 084 * Constructs a new {@link RowSetDynaClass} for the specified {@code ResultSet}. The property names corresponding to column names in the result set will be 085 * lower cased. 086 * </p> 087 * 088 * @param resultSet The result set to be wrapped 089 * @throws NullPointerException if {@code resultSet} is {@code null} 090 * @throws SQLException if the metadata for this result set cannot be introspected 091 */ 092 public RowSetDynaClass(final ResultSet resultSet) throws SQLException { 093 this(resultSet, true, -1); 094 } 095 096 /** 097 * <p> 098 * Constructs a new {@link RowSetDynaClass} for the specified {@code ResultSet}. The property names corresponding to the column names in the result set will 099 * 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}