RowSetDynaClass.java

  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. package org.apache.commons.beanutils2.sql;

  18. import java.sql.ResultSet;
  19. import java.sql.SQLException;
  20. import java.util.ArrayList;
  21. import java.util.List;
  22. import java.util.Objects;

  23. import org.apache.commons.beanutils2.BasicDynaBean;
  24. import org.apache.commons.beanutils2.DynaBean;
  25. import org.apache.commons.beanutils2.DynaClass;
  26. import org.apache.commons.beanutils2.DynaProperty;

  27. /**
  28.  * <p>
  29.  * Implements {@link DynaClass} to create an in-memory collection of {@link DynaBean}s representing the results of an SQL query. Once the {@link DynaClass}
  30.  * 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
  31.  * returned to its connection pool (if you are using one).
  32.  * </p>
  33.  *
  34.  * <p>
  35.  * The normal usage pattern is something like:
  36.  * </p>
  37.  *
  38.  * <pre>
  39.  *   Connection conn = ...;  // Acquire connection from pool
  40.  *   Statement stmt = conn.createStatement();
  41.  *   ResultSet rs = stmt.executeQuery("SELECT ...");
  42.  *   RowSetDynaClass rsdc = new RowSetDynaClass(rs);
  43.  *   rs.close();
  44.  *   stmt.close();
  45.  *   ...;                    // Return connection to pool
  46.  *   List rows = rsdc.getRows();
  47.  *   ...;                   // Process the rows as desired
  48.  * </pre>
  49.  *
  50.  * <p>
  51.  * 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).
  52.  * There will be one {@link DynaBean} in the {@code List</code> returned by <code>getRows()} for each row in the original {@code ResultSet}.
  53.  * </p>
  54.  *
  55.  * <p>
  56.  * In general, instances of {@link RowSetDynaClass} can be serialized and deserialized, which will automatically include the list of {@link DynaBean}s
  57.  * representing the data content. The only exception to this rule would be when the underlying property values that were copied from the {@code ResultSet}
  58.  * originally cannot themselves be serialized. Therefore, a {@link RowSetDynaClass} makes a very convenient mechanism for transporting data sets to remote
  59.  * Java-based application components.
  60.  * </p>
  61.  */
  62. public class RowSetDynaClass extends AbstractJdbcDynaClass {

  63.     /**
  64.      * <p>
  65.      * 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
  66.      * the size of the result.
  67.      */
  68.     protected int limit = -1;

  69.     /**
  70.      * <p>
  71.      * The list of {@link DynaBean}s representing the contents of the original {@code ResultSet} on which this {@link RowSetDynaClass} was based.
  72.      * </p>
  73.      */
  74.     protected List<DynaBean> rows = new ArrayList<>();

  75.     /**
  76.      * <p>
  77.      * Constructs a new {@link RowSetDynaClass} for the specified {@code ResultSet}. The property names corresponding to column names in the result set will be
  78.      * lower cased.
  79.      * </p>
  80.      *
  81.      * @param resultSet The result set to be wrapped
  82.      * @throws NullPointerException if {@code resultSet} is {@code null}
  83.      * @throws SQLException         if the metadata for this result set cannot be introspected
  84.      */
  85.     public RowSetDynaClass(final ResultSet resultSet) throws SQLException {
  86.         this(resultSet, true, -1);
  87.     }

  88.     /**
  89.      * <p>
  90.      * Constructs a new {@link RowSetDynaClass} for the specified {@code ResultSet}. The property names corresponding to the column names in the result set will
  91.      * be lower cased or not, depending on the specified {@code lowerCase} value.
  92.      * </p>
  93.      *
  94.      * If {@code limit</code> is not less than 0, max <code>limit} number of rows will be copied into the resultset.
  95.      *
  96.      *
  97.      * @param resultSet The result set to be wrapped
  98.      * @param lowerCase Should property names be lower cased?
  99.      * @throws NullPointerException if {@code resultSet} is {@code null}
  100.      * @throws SQLException         if the metadata for this result set cannot be introspected
  101.      */
  102.     public RowSetDynaClass(final ResultSet resultSet, final boolean lowerCase) throws SQLException {
  103.         this(resultSet, lowerCase, -1);
  104.     }

  105.     /**
  106.      * <p>
  107.      * Constructs a new {@link RowSetDynaClass} for the specified {@code ResultSet}. The property names corresponding to the column names in the result set will
  108.      * be 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.      * @param useColumnLabel true if the column label should be used, otherwise false
  120.      * @throws NullPointerException if {@code resultSet} is {@code null}
  121.      * @throws SQLException         if the metadata for this result set cannot be introspected
  122.      * @since 1.8.3
  123.      */
  124.     public RowSetDynaClass(final ResultSet resultSet, final boolean lowerCase, final boolean useColumnLabel) throws SQLException {
  125.         this(resultSet, lowerCase, -1, useColumnLabel);
  126.     }

  127.     /**
  128.      * <p>
  129.      * Constructs a new {@link RowSetDynaClass} for the specified {@code ResultSet}. The property names corresponding to the column names in the result set will
  130.      * be lower cased or not, depending on the specified {@code lowerCase} value.
  131.      * </p>
  132.      *
  133.      * <p>
  134.      * <strong>WARNING</strong> - If you specify {@code false} for {@code lowerCase}, the returned property names will exactly match the column names returned
  135.      * by your JDBC driver. Because different drivers might return column names in different cases, the property names seen by your application will vary
  136.      * depending on which JDBC driver you are using.
  137.      * </p>
  138.      *
  139.      * @param resultSet The result set to be wrapped
  140.      * @param lowerCase Should property names be lower cased?
  141.      * @param limit     Maximum limit for the {@code List} of {@link DynaBean}
  142.      * @throws NullPointerException if {@code resultSet} is {@code null}
  143.      * @throws SQLException         if the metadata for this result set cannot be introspected
  144.      */
  145.     public RowSetDynaClass(final ResultSet resultSet, final boolean lowerCase, final int limit) throws SQLException {
  146.         this(resultSet, lowerCase, limit, false);
  147.     }

  148.     /**
  149.      * <p>
  150.      * Constructs a new {@link RowSetDynaClass} for the specified {@code ResultSet}. The property names corresponding to the column names in the result set will
  151.      * be lower cased or not, depending on the specified {@code lowerCase} value.
  152.      * </p>
  153.      *
  154.      * <p>
  155.      * <strong>WARNING</strong> - If you specify {@code false} for {@code lowerCase}, the returned property names will exactly match the column names returned
  156.      * by your JDBC driver. Because different drivers might return column names in different cases, the property names seen by your application will vary
  157.      * depending on which JDBC driver you are using.
  158.      * </p>
  159.      *
  160.      * @param resultSet      The result set to be wrapped
  161.      * @param lowerCase      Should property names be lower cased?
  162.      * @param limit          Maximum limit for the {@code List} of {@link DynaBean}
  163.      * @param useColumnLabel true if the column label should be used, otherwise false
  164.      * @throws NullPointerException if {@code resultSet} is {@code null}
  165.      * @throws SQLException         if the metadata for this result set cannot be introspected
  166.      * @since 1.8.3
  167.      */
  168.     @SuppressWarnings("resource") // resultSet is not allocated here
  169.     public RowSetDynaClass(final ResultSet resultSet, final boolean lowerCase, final int limit, final boolean useColumnLabel) throws SQLException {
  170.         Objects.requireNonNull(resultSet, "resultSet");
  171.         this.lowerCase = lowerCase;
  172.         this.limit = limit;
  173.         setUseColumnLabel(useColumnLabel);
  174.         introspect(resultSet);
  175.         copy(resultSet);
  176.     }

  177.     /**
  178.      * <p>
  179.      * Constructs a new {@link RowSetDynaClass} for the specified {@code ResultSet}. The property names corresponding to column names in the result set will be
  180.      * lower cased.
  181.      * </p>
  182.      *
  183.      * If {@code limit</code> is not less than 0, max <code>limit} number of rows will be copied into the list.
  184.      *
  185.      * @param resultSet The result set to be wrapped
  186.      * @param limit     The maximum for the size of the result.
  187.      * @throws NullPointerException if {@code resultSet} is {@code null}
  188.      * @throws SQLException         if the metadata for this result set cannot be introspected
  189.      */
  190.     public RowSetDynaClass(final ResultSet resultSet, final int limit) throws SQLException {
  191.         this(resultSet, true, limit);
  192.     }

  193.     /**
  194.      * <p>
  195.      * 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
  196.      * {@link DynaBean}s that will later by returned by a call to {@code getRows()}.
  197.      * </p>
  198.      *
  199.      * @param resultSet The {@code ResultSet} whose data is to be copied
  200.      * @throws SQLException if an error is encountered copying the data
  201.      */
  202.     protected void copy(final ResultSet resultSet) throws SQLException {
  203.         int cnt = 0;
  204.         while (resultSet.next() && (limit < 0 || cnt++ < limit)) {
  205.             final DynaBean bean = createDynaBean();
  206.             for (final DynaProperty property : properties) {
  207.                 final String name = property.getName();
  208.                 final Object value = getObject(resultSet, name);
  209.                 bean.set(name, value);
  210.             }
  211.             rows.add(bean);
  212.         }
  213.     }

  214.     /**
  215.      * <p>
  216.      * Create and return a new {@link DynaBean} instance to be used for representing a row in the underlying result set.
  217.      * </p>
  218.      *
  219.      * @return A new {@code DynaBean} instance
  220.      */
  221.     protected DynaBean createDynaBean() {
  222.         return new BasicDynaBean(this);
  223.     }

  224.     /**
  225.      * <p>
  226.      * 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
  227.      * this {@link RowSetDynaClass} instance. These {@link DynaBean}s are disconnected from the database itself, so there is no problem with modifying the
  228.      * 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
  229.      * changes back to the database, if it so desires.
  230.      * </p>
  231.      *
  232.      * @return A {@code List} of {@link DynaBean} instances
  233.      */
  234.     public List<DynaBean> getRows() {
  235.         return this.rows;
  236.     }

  237. }