作用:
匹配列名到Bean属性名,并转换结果集列到Bean对象的属性中
解析:
package org.apache.commons.dbutils;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLXML;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* BeanProcessor 类将列名和JavaBean属性名匹配,并将 ResultSet 列的值转换为 JavaBean 的属性的值
* 子类应该覆盖此类方法以定制行为
* 这个类是线程安全的
*/
public class BeanProcessor {
/**
* 由 mapColumnsToProperties 方法使用的特殊的数组的值
* 该值表示没有 JavaBean 属性和 ResultSet 列的值对应
*/
protected static final int PROPERTY_NOT_FOUND = -1;
/**
* 当返回SQL NULL 时,将 JavaBean 的原始属性设置为这些默认值
* 这些默认值和 ResultSet 的getXXX() 方法在空列时返回的值相同
*/
private static final Map, Object> primitiveDefaults = new HashMap, Object>();
/**
* ResultSet 列到 JavaBean 属性的映射
* columnName:propertyName
*/
private final Map columnToPropertyOverrides;
//设置默认值
static {
primitiveDefaults.put(Integer.TYPE, Integer.valueOf(0));
primitiveDefaults.put(Short.TYPE, Short.valueOf((short) 0));
primitiveDefaults.put(Byte.TYPE, Byte.valueOf((byte) 0));
primitiveDefaults.put(Float.TYPE, Float.valueOf(0f));
primitiveDefaults.put(Double.TYPE, Double.valueOf(0d));
primitiveDefaults.put(Long.TYPE, Long.valueOf(0L));
primitiveDefaults.put(Boolean.TYPE, Boolean.FALSE);
primitiveDefaults.put(Character.TYPE, Character.valueOf((char) 0));
}
/**
* BeanProcessor 的构造函数
*/
public BeanProcessor() {
this(new HashMap());
}
/**
* BeanProcessor 的构造函数
* columnToPropertyOverrides:列:属性
*/
public BeanProcessor(Map columnToPropertyOverrides) {
super();
if (columnToPropertyOverrides == null) {
throw new IllegalArgumentException("columnToPropertyOverrides map cannot be null");
}
this.columnToPropertyOverrides = columnToPropertyOverrides;
}
/**
* 将 ResultSet 的一行转换为一个 JavaBean
* 该实现利用反射和 BeanInfo 类来将列名和 JavaBean 属性名匹配
* 该匹配基于如下几个因素:
* 1、
* 该类具有一个可写属性,其名称与列相同
* 名称比较是不区分大小写的
* 2、
* 可以使用 ResultSet 的 getXXX() 方法得到的列类型转换为属性的setXXX方法参数的类型
* 如果转换失败(如:属性为 int 类型,列为 Timestamp 类型),将抛出 SQLException 异常
*
* 当从 ResultSet 返回SQL NULL时,将原始 JavaBean 属性设置为默认值
* 数字字段设置为0,而布尔值设置为false
* 当返回SQL null时,Object 属性被设置为null
* 这与ResultSet 的 getXXX() 方法表现相同
* :想要创建的 JavaBean 的类型
* rs:提供 JavaBean 数据的 ResultSet 类
* type: 创建 JavaBean 实例的类
*/
public T toBean(ResultSet rs, Class type) throws SQLException {
//获得 type 的属性描述符 PropertyDescriptor[]
//类似于:java.beans.PropertyDescriptor[name=age; propertyType=int; readMethod=public int com.xiya.entity.Person.getAge(); writeMethod=public void com.xiya.entity.Person.setAge(int)]
PropertyDescriptor[] props = this.propertyDescriptors(type);
//获得 ResultSet 的元信息
ResultSetMetaData rsmd = rs.getMetaData();
//获得 列 到 属性 的映射信息(也就是某一列对应于哪一个PropertyDescriptor)
int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);
return this.createBean(rs, type, props, columnToProperty);
}
/**
* 将 ResultSet 转换为 List
*/
public List toBeanList(ResultSet rs, Class type) throws SQLException {
List results = new ArrayList();
//如果 ResultSet 结果集为空,则返回空集合
if (!rs.next()) {
return results;
}
//见上
PropertyDescriptor[] props = this.propertyDescriptors(type);
ResultSetMetaData rsmd = rs.getMetaData();
int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);
//将 JavaBean 添加进集合
do {
results.add(this.createBean(rs, type, props, columnToProperty));
} while (rs.next());
return results;
}
/**
* 创建一个新对象,并从 ResultSet 中获取字段数据用来初始化该对象
* :想要创建的 JavaBean 的类型
* rs:ResultSet
* type:返回的对象的类型
* props:属性描述符
* columnToProperty:结果集中的列索引
* 返回初始化后的对象
* 数据库访问出错将抛出 SQLException 异常
*/
private T createBean(ResultSet rs, Class type,
PropertyDescriptor[] props, int[] columnToProperty)
throws SQLException {
T bean = this.newInstance(type);
for (int i = 1; i < columnToProperty.length; i++) {
if (columnToProperty[i] == PROPERTY_NOT_FOUND) {
continue;
}
// 获得属性描述符
// 类似于:java.beans.PropertyDescriptor[name=age; propertyType=int; readMethod=public int com.xiya.entity.Person.getAge();
// writeMethod=public void com.xiya.entity.Person.setAge(int)]
PropertyDescriptor prop = props[columnToProperty[i]];
//获得属性的类型
Class> propType = prop.getPropertyType();
Object value = null;
if(propType != null) {
//获得当前列的值
value = this.processColumn(rs, i, propType);
if (value == null && propType.isPrimitive()) {
value = primitiveDefaults.get(propType);
}
}
//调用setter方法
this.callSetter(bean, prop, value);
}
return bean;
}
/**
* 调用给定的setter方法
* 如果属性不存在setter方法,则此方法不执行任何操作
*/
private void callSetter(Object target, PropertyDescriptor prop, Object value)
throws SQLException {
Method setter = prop.getWriteMethod();
if (setter == null) {
return;
}
Class>[] params = setter.getParameterTypes();
try {
// convert types for some popular ones
if (value instanceof java.util.Date) {
final String targetType = params[0].getName();
if ("java.sql.Date".equals(targetType)) {
value = new java.sql.Date(((java.util.Date) value).getTime());
} else if ("java.sql.Time".equals(targetType)) {
value = new java.sql.Time(((java.util.Date) value).getTime());
} else if ("java.sql.Timestamp".equals(targetType)) {
Timestamp tsValue = (Timestamp) value;
int nanos = tsValue.getNanos();
value = new java.sql.Timestamp(tsValue.getTime());
((Timestamp) value).setNanos(nanos);
}
} else if (value instanceof String && params[0].isEnum()) {
value = Enum.valueOf(params[0].asSubclass(Enum.class), (String) value);
}
// Don't call setter if the value object isn't the right type
if (this.isCompatibleType(value, params[0])) {
setter.invoke(target, new Object[]{value});
} else {
throw new SQLException(
"Cannot set " + prop.getName() + ": incompatible types, cannot convert "
+ value.getClass().getName() + " to " + params[0].getName());
// value cannot be null here because isCompatibleType allows null
}
} catch (IllegalArgumentException e) {
throw new SQLException(
"Cannot set " + prop.getName() + ": " + e.getMessage());
} catch (IllegalAccessException e) {
throw new SQLException(
"Cannot set " + prop.getName() + ": " + e.getMessage());
} catch (InvocationTargetException e) {
throw new SQLException(
"Cannot set " + prop.getName() + ": " + e.getMessage());
}
}
/**
* ResultSet 的 getObject() 对于 INT 类型的列返回 Integer 类型的对象
* JavaBean属性的setter方法可以使用 Integer 或者 int 类型的参数
*
* 如果 value 值可以成功地传递到setter方法,则返回 true
* Method.invoke() 将 Integer 解包处理成 int
*
* value:将被传递到setter方法的值
* type:setter的参数类型(非空)
*/
private boolean isCompatibleType(Object value, Class> type) {
// Do object check first, then primitives
if (value == null || type.isInstance(value)) {
return true;
} else if (type.equals(Integer.TYPE) && value instanceof Integer) {
return true;
} else if (type.equals(Long.TYPE) && value instanceof Long) {
return true;
} else if (type.equals(Double.TYPE) && value instanceof Double) {
return true;
} else if (type.equals(Float.TYPE) && value instanceof Float) {
return true;
} else if (type.equals(Short.TYPE) && value instanceof Short) {
return true;
} else if (type.equals(Byte.TYPE) && value instanceof Byte) {
return true;
} else if (type.equals(Character.TYPE) && value instanceof Character) {
return true;
} else if (type.equals(Boolean.TYPE) && value instanceof Boolean) {
return true;
}
return false;
}
/**
* 返回给定 Class 的新实例的工厂方法
* 这在bean创建过程的开始处被调用,并且可以被覆盖,以提供类似返回缓存bean实例的自定义行为
*/
protected T newInstance(Class c) throws SQLException {
try {
return c.newInstance();
} catch (InstantiationException e) {
throw new SQLException(
"Cannot create " + c.getName() + ": " + e.getMessage());
} catch (IllegalAccessException e) {
throw new SQLException(
"Cannot create " + c.getName() + ": " + e.getMessage());
}
}
/**
* 对于给定的Class 返回 PropertyDescriptor[]
*/
private PropertyDescriptor[] propertyDescriptors(Class> c)
throws SQLException {
// Introspector 缓存 BeanInfo 以获得更好的性能
BeanInfo beanInfo = null;
try {
beanInfo = Introspector.getBeanInfo(c);
} catch (IntrospectionException e) {
throw new SQLException(
"Bean introspection failed: " + e.getMessage());
}
return beanInfo.getPropertyDescriptors();
}
/**
* 返回的数组中的索引下标表示的是列的编号
* 在数组中每个位置存储的值表示与列名相匹配的bean属性在 PropertyDescriptor[] 中的位置索引
* 如果在列中没有发现bean属性,则将该位置设置为PROPERTY_NOT_FOUND
*
* 返回一个int[],表示列索引到属性索引的映射
* 位置在[0]的元素是没有意义的,因为JDBC 列索引从1开始
*/
protected int[] mapColumnsToProperties(ResultSetMetaData rsmd,
PropertyDescriptor[] props) throws SQLException {
//返回列的数目
int cols = rsmd.getColumnCount();
int[] columnToProperty = new int[cols + 1];
Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);
for (int col = 1; col <= cols; col++) {
//返回列的别名
String columnName = rsmd.getColumnLabel(col);
if (null == columnName || 0 == columnName.length()) {
//返回列名
columnName = rsmd.getColumnName(col);
}
//通过列名查找属性名
String propertyName = columnToPropertyOverrides.get(columnName);
if (propertyName == null) {
propertyName = columnName;
}
for (int i = 0; i < props.length; i++) {
if (propertyName.equalsIgnoreCase(props[i].getName())) {
//当前列对应 PropertyDescriptor[] 的位置
columnToProperty[col] = i;
break;
}
}
}
return columnToProperty;
}
/**
* 将 ResultSet 列转换为对象
* 简单的实现可以调用 rs.getObject(index)
* 复杂的实现可以先执行类型操作,将列的类型和bean属性的类型匹配
*
* 对于给定的属性类型,这个实现调用适当ResultSet getter方法来执行类型转换
* 如果属性类型和 ResultSet 所支持的类型不匹配,则调用getObject()
*
* rs:当前处理的 ResultSet,在传递给该方法之前,它必须指向一个有效的行
* index: 当前处理的列的索引
* propType:列需要转换成的bean的属性的类型
* 数据库访问出错将抛出 SQLException 异常
* 返回给定列索引的对象,如果列值为空则返回null
*/
protected Object processColumn(ResultSet rs, int index, Class> propType)
throws SQLException {
//判断propType是不是原始类型(int/double/byte/void等9个类型)
if ( !propType.isPrimitive() && rs.getObject(index) == null ) {
return null;
}
if (propType.equals(String.class)) {
return rs.getString(index);
} else if (
propType.equals(Integer.TYPE) || propType.equals(Integer.class)) {
return Integer.valueOf(rs.getInt(index));
} else if (
propType.equals(Boolean.TYPE) || propType.equals(Boolean.class)) {
return Boolean.valueOf(rs.getBoolean(index));
} else if (propType.equals(Long.TYPE) || propType.equals(Long.class)) {
return Long.valueOf(rs.getLong(index));
} else if (
propType.equals(Double.TYPE) || propType.equals(Double.class)) {
return Double.valueOf(rs.getDouble(index));
} else if (
propType.equals(Float.TYPE) || propType.equals(Float.class)) {
return Float.valueOf(rs.getFloat(index));
} else if (
propType.equals(Short.TYPE) || propType.equals(Short.class)) {
return Short.valueOf(rs.getShort(index));
} else if (propType.equals(Byte.TYPE) || propType.equals(Byte.class)) {
return Byte.valueOf(rs.getByte(index));
} else if (propType.equals(Timestamp.class)) {
return rs.getTimestamp(index);
} else if (propType.equals(SQLXML.class)) {
return rs.getSQLXML(index);
} else {
return rs.getObject(index);
}
}
}
BeanProcessor
存在一个子类GenerousBeanProcessor
,重写了父类的mapColumnsToProperties
方法。
package org.apache.commons.dbutils;
import java.beans.PropertyDescriptor;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Arrays;
/**
* 提供数据库列到JavaBean属性的名称匹配
*/
public class GenerousBeanProcessor extends BeanProcessor {
/**
* 默认构造函数
*/
public GenerousBeanProcessor() {
super();
}
@Override
protected int[] mapColumnsToProperties(final ResultSetMetaData rsmd,
final PropertyDescriptor[] props) throws SQLException {
final int cols = rsmd.getColumnCount();
final int[] columnToProperty = new int[cols + 1];
Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);
for (int col = 1; col <= cols; col++) {
String columnName = rsmd.getColumnLabel(col);
if (null == columnName || 0 == columnName.length()) {
columnName = rsmd.getColumnName(col);
}
final String generousColumnName = columnName.replace("_", "");
for (int i = 0; i < props.length; i++) {
final String propName = props[i].getName();
// see if either the column name, or the generous one matches
if (columnName.equalsIgnoreCase(propName) ||
generousColumnName.equalsIgnoreCase(propName)) {
columnToProperty[col] = i;
break;
}
}
}
return columnToProperty;
}
}