(KenFitz)
This code uses Apache's BeanUtils because Spring's equivalent
isn't sufficiently forgiving ...
Also uses Apache's Logging & Lang for the same reason.
Code:
/*
* JavaBeanResultSetExtractor
* Created on Dec 1, 2004
*
* Copyright 2002-2004 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kenfitz.springframework.jdbc;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.ResultSetDynaClass;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
// TODO Spring's BeanUtils.copyProperties is not sufficiently forgiving. must use Apache's
// import org.springframework.beans.BeanUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.util.ClassUtils;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* JavaBeanResultSetExtractor
* implements Spring's ResultSetExtractor interface
* in a reusable manner,
* providing a single solution for
* processing a ResultSet and returning populated Java Bean instances.
* JavaBeanResultSetExtractor is typically used in conjunction with Spring's JdbcTemplate,
* although it can be used in any
* Spring context where an implementation of ResultSetExtractor is used.
* </p>
* <p>
* Use of JavaBeanResultSetExtractor is a single alternative to
* repeatedly coding in-line anonymous classes that
* also implement the ResultSetExtractor interface.
* </p>
* <p>
* An example of using this class is:
* <pre>
* <code>
* Object[] sqlBindParameters = { userId };
JdbcTemplate jdbcTemplate = new JdbcTemplate(this.getDataSource());
List users = (List) jdbcTemplate.query(
"select userId, firstName, lastName from users where userId = ?",
sqlBindParameters,
<b>new JavaBeanResultSetExtractor( User.class )</b> );
* </code>
* </pre>
* Normally, an in-line class implementation of
* the ResultSetExtractor interface
* would appear where
* <code>
* new JavaBeanResultSetExtractor( User.class )
* </code>
* appears above.
* <p>
* </p>
* Note that the Column Names of the SQL Select clause
* must follow JavaBean property naming conventions
* for the successful population of the target Java Bean properties.
* This may prompt the need for establishing Column Name Aliases,
* if the Column Names don't naturally meet this requirement.
* <br/>
* For example:
* <pre>
* <code>
* "select USER_ID <b>as userId</b>, FIRST_NAME <b>as firstName</b>, LAST_NAME <b>as lastName</b> from users " +
* "where USER_ID = ?"
* </code>
* </pre>
* The JavaBeanResultSetExtractor constructor
* <code>
* JavaBeanResultSetExtractor(Class)
* </code>
* can be used to create the JavaBeanResultSetExtractor instance
* while simultaneously establishing the Class of the target Java Bean.
* The default constructor also can be used,
* but it is necessary to have established the
* Class of the target Java Bean (through the
* <code>
* setBeanClass(Class)
* </code>
* method prior to executing the
* <code>
* extractData(ResultSet)
* </code>
* method.
* An
* <code>
* IllegalStateException
* </code>
* is thrown if the
* <code>
* beanClass
* </code>
* property remains unspecified at the time that the
* <code>
* extractData(ResultSet)
* </code>
* method is executed.
* <p>
* The returned value is always a
* <code>
* List,
* </code>
* which may be empty.
* </p>
* <p>
* JavaBeanResultSetExtractor has a dependency on the
* Apache/Jakarta/Commons/BeanUtils component.
* As such, the beanutils.jar is required.
* </p>
*
* @author Ken Fitzpatrick, Boerne, Texas, USA
* @see <a href="http://www.springframework.org/docs/api/org/springframework/jdbc/core/JdbcTemplate.html" >JdbcTemplate</a>
* @see <a href="http://www.springframework.org/docs/api/org/springframework/jdbc/core/ResultSetExtractor.html" >ResultSetExtractor</a>
* @see <a href="http://jakarta.apache.org/commons/beanutils/apidocs/org/apache/commons/beanutils/DynaBean.html" >Apache/Jakarta/Commons/BeanUtils DynaBean</a>
* @see <a href="hhttp://jakarta.apache.org/commons/beanutils/apidocs/org/apache/commons/beanutils/ResultSetDynaClass.html" >Apache/Jakarta/Commons/BeanUtils ResultSetDynaClass</a>
* @see <a href="http://jakarta.apache.org/commons/beanutils/">Apache/Jakarta/Commons/BeanUtils</a>
*/
public class JavaBeanResultSetExtractor implements ResultSetExtractor
{
private Class beanClass;
private List beans = new ArrayList();
private static Log log = LogFactory.getLog( JavaBeanResultSetExtractor.class );
private static String PACKAGE_NAME = ClassUtils.getShortName( JavaBeanResultSetExtractor.class );
private static String CLASS_NAME = ClassUtils.getShortName( JavaBeanResultSetExtractor.class );
/**
* Constructs a new instance of
* <code>JavaBeanResultSetExtractor</code>.
* Since no
* <code>beanClass</code>
* is provided to this constructor,
* the user must execute the
* <code>setBeanClass(Class)</code>
* method prior to using this instance
* against a ResultSet.
*/
public JavaBeanResultSetExtractor()
{
super();
}
/**
* Constructs a new instance of
* <code>JavaBeanResultSetExtractor</code>,
* setting the beanClass property to the value found in
* <code>beanClass</code>.
* @param beanClass Class of the target bean to be created, populated and returned
* while processing the ResultSet.
*/
public JavaBeanResultSetExtractor( Class beanClass )
{
this();
this.setBeanClass(beanClass);
}
/**
* Returns a <code>List</code> containing instances of the
* target Java Bean Class defined in the
* <code>beanClass</code> property,
* where those beans have been populated from the
* contents of
* <code>resultSet</code>,
* using normal Java Bean Property Naming conventions.
* <p>
* An
* <code>
* IllegalStateException
* </code>
* is thrown if the
* <code>
* beanClass
* </code>
* property remains unspecified at the time that this method is executed.
* </p>
* @param resultSet ResultSet that has not yet been processed or moved from its origin.
* @return List of populated instances of the target class. May be an empty list.
* @see org.springframework.jdbc.core.ResultSetExtractor#extractData(java.sql.ResultSet)
*/
public Object extractData( ResultSet resultSet )
throws SQLException, DataAccessException
{
final String METHOD_NAME = CLASS_NAME + ".extractData: ";
// beanClass must be defined
if ( this.getBeanClass() == null )
throw new IllegalStateException( METHOD_NAME +
"Property 'beanClass' must not be null when this method is executed. " +
"Use the JavaBeanResultSetExtractor(Class) constructor or " +
"subsequently execute the setBeanClass(Class) method before executing this method." );
if ( log.isDebugEnabled() )
log.debug(METHOD_NAME +
"Using beanClass=" + this.getBeanClass() +
"");
// create a ResultSetDynaClass from resultSet
ResultSetDynaClass rsdc = new ResultSetDynaClass(resultSet,false);
Iterator rows = rsdc.iterator();
List beans = this.getBeans();
int successfullyProcessedRows = 0;
// process resultSet
while (rows.hasNext())
{
Object bean = null;
DynaBean rowDynaBean = null;
try
{
// create instance of target class
bean = this.getBeanClass().newInstance();
// get next row, wrapped as a DynaBean
rowDynaBean = (DynaBean) rows.next();
// copy like-named properties from the DynaBean to the target bean
BeanUtils.copyProperties( bean, rowDynaBean );
// accumulate beans
beans.add(bean);
successfullyProcessedRows++;
}
catch ( Exception e )
{
String message = METHOD_NAME + "Error occurred while attempting to " +
" copy row values into new instance of " +
"beanClass=" + this.getBeanClass() +
". At the time of the error" +
", bean=" + ( bean == null ? "null" : ReflectionToStringBuilder.toString(bean) ) +
", rowDynaBean=" + ( rowDynaBean == null ? "null" : ReflectionToStringBuilder.toString(rowDynaBean) ) +
", error=" + e + "";
log.error( message, e );
}
}
if ( log.isDebugEnabled() )
log.debug(METHOD_NAME +
"Successfully processed " + successfullyProcessedRows + " rows" +
", returning beans.size=" + beans.size() +
" of class=" + this.getBeanClass() +
"");
return beans;
}
/**
* Returns the
* <code>beanClass</code>
* property.
* @return Class
*/
public Class getBeanClass()
{
return this.beanClass;
}
/**
* Sets the
* <code>beanClass</code>
* property to the value found in
* <code>beanClass</code>.
* @param beanClass Class to be used to set the beanClass property.
*/
public void setBeanClass( Class beanClass )
{
this.beanClass = beanClass;
}
/**
* Returns the
* <code>beans</code>
* property.
* @return List
*/
public List getBeans()
{
return this.beans;
}
/**
* Sets the
* <code>beans</code>
* property to the value found in
* <code>beans</code>.
* @param beans List to be used to set the beans property.
*/
public void setBeans( List beans )
{
this.beans = beans;
}
}