【源码阅读】dbutil包中的BeanProcessor类

BeanProcessor通过名字就可以知道,该类是用来处理Bean类(JavaBean)的,它的作用是将数据库中的记录转化为对应的Bean对象。

下面先看一下该类的类图:

该类有三个成员变量:我们重点来看两个Map类型的变量:

1、primitiveDefaults:该变量的主要作用是保存Java基本数据类型的Class与其默认值的对应关系,然后当SQL语句get方法返回NULL值的时候就用从该Map中获取相应的默认值,下面的静态代码块用来初始化该Map:

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));
    }

也就是说,如果我的一个JavaBean中有一个属性的类型为int或者double这样的基本数据类型,那么如果数据库中对应的字段值为NULL,那么此时,我们需要给该属性一个默认值,此时就需要从该Map中获取不同类型对应的字段值。

2、columnToPropertyOverrides:通过名字也可以看出,该变量的作用是存放数据库字段名与Bean的属性名之间的对应关系。而Overrides仿佛告诉我们,这个变量是可以被用户“重写”的,也就是说:用户可以自己定义字段名与属性值的关系。通过BeanProcessor的构造器我们可以看出,该变量的值允许用户传递进来,也就是说,用户可以根据自己的需要提供一组对应关系,用来管理数据库字段到bean属性的映射。

为什么要维护这样一个关系呢?因为在程序把数据库字段赋值给Bean属性的时候,它们无法知道数据库中的一个字段应该赋值给Bean的哪个属性,所以需要提供一个这样的对应规则。

该变量没有初始化过程,如果用户传递进来了一个Map,那么将使用用户的转换规则,否则就使用默认的转换规则:当数据库字段名与JavaBean的属性名相同的时候才给相应属性赋值,否则忽略该属性,后面方法里会仔细说明这个问题。

下面是该类的两个构造函数:

/**
     * Constructor for BeanProcessor.
     */
    public BeanProcessor() {
        this(new HashMap<String, String>());
    }

    /**
     * Constructor for BeanProcessor configured with column to property name overrides.
     *
     * @param columnToPropertyOverrides ResultSet column to bean property name overrides
     * @since 1.5
     */
    public BeanProcessor(Map<String, String> columnToPropertyOverrides) {
        super();
        if (columnToPropertyOverrides == null) {
            throw new IllegalArgumentException("columnToPropertyOverrides map cannot be null");
        }
        this.columnToPropertyOverrides = columnToPropertyOverrides;
    }
可以看出,允许用户自定义一个Map,用来维护数据库字段名与Bean属性名之间的关系。


接下来我们来说明一下该类的几个方法:

toBean(ResultSet rs, Class<T> type):该方法最能体现该类的功能,看名字就知道,它的作用是将ResultSet中的一条记录转化成type类型的Bean对象,那它是如何实现的呢?


/**
     * 将ResultSet中的一行转化成一个JavaBean对象。该方法的实现用到了反射和BeanInfo类
     * 来将数据库字段名与JavaBean属性名进行匹配.属性被匹配到字段名基于以下因素:
     * <br/>
     * <ol>
     *     <li>
     *     JavaBean类中存在一个与字段名同名的属性,并且该属性是可写的(即提供一个可访问的set方法).
     *     名称的比较是忽略大小写的.
     *     </li>
     *
     *     <li>
     *     通过ResultSet的get*方法获得的字段的类型可以被转化成类属性的set方法的参数类型.  如果转换失败了
     *     (比如. Bean属性的类型是int 而数据字段的类型为Timestamp) 那么将抛出一个SQLException异常
     *     </li>
     * </ol>
     *
     * <p>
     * 当ResultSet中返回NULL值的时候,JavaBean中的基本数据类型的属性将被赋予它们对应的默认值,
     * 数字类型的将被赋值为0,布尔类型的将被赋值为false,
     * 对象类型的Bean属性将被赋值为null.这与ResultSet的get*方法的行为是一致的
     * </p>
     * @param <T> 需要创建的Bean的类型
     * @param rs 提供数据的ResultSet
     * @param type 创建Bean对象所需要的Class类
     * @throws SQLException if a database access error occurs
     * @return the newly created bean
     */
    public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException {

        PropertyDescriptor[] props = this.propertyDescriptors(type);

        ResultSetMetaData rsmd = rs.getMetaData();
        int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);

        return this.createBean(rs, type, props, columnToProperty);
    }


该类的方法中用到了一个mapColumnsToProperties方法,该方法的主要作用是:返回一个int数组,数组中存放的是与数据库字段对应的属性的索引,通过该索引来从props数组中获取某个对应的属性来与数据库中的某个字段进行匹配,如下:

该方法返回一个数组,数组包含两个信息,一个是数组元素在数组中的位置:这个位置信息表示的是ResultSet中某个字段的位置;另一个信息就是数组中元素的值:该值存在的是PropertyDescriptor[]数组中的一个索引,该索引用来标识一个PropertyDescriptor,该PropertyDescriptor保存的属性字段信息与当前元素位置表示的那个Column是对应的。


/**
     * The positions in the returned array represent column numbers.  The
     * values stored at each position represent the index in the
     * <code>PropertyDescriptor[]</code> for the bean property that matches
     * the column name.  If no bean property was found for a column, the
     * position is set to <code>PROPERTY_NOT_FOUND</code>.
     *
     * @param rsmd The <code>ResultSetMetaData</code> containing column
     * information.
     *
     * @param props The bean property descriptors.
     *
     * @throws SQLException if a database access error occurs
     *
     * @return An int[] with column index to property index mappings.  The 0th
     * element is meaningless because JDBC column indexing starts at 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); //可以看到,它会先从columnToPropertyOverrides中根据字段名获取属性名,如果为空,说明用户没有为该map提供值,或者该map中没有该对应关系       if (propertyName == null) {
                propertyName = columnName;           //如果没有找到对应关系,那么将使用默认的:也就是认为属性名与字段名是一样的
            }
            for (int i = 0; i < props.length; i++) {       //循环,查找与当前ColumnName对应的PropertyDescriptor,从而将其索引存入数组

                if (propertyName.equalsIgnoreCase(props[i].getName())) {
                    columnToProperty[col] = i;
                    break;
                }
            }
        }

        return columnToProperty;              //返回数组
    }

找到对应关系之后,toBean方法又调用了createBean方法:

该方法的作用才是真正的创建Bean对象的过程:

/**
     * Creates a new object and initializes its fields from the ResultSet.
     * @param <T> The type of bean to create
     * @param rs The result set.
     * @param type The bean type (the return type of the object).
     * @param props The property descriptors.
     * @param columnToProperty The column indices in the result set.
     * @return An initialized object.
     * @throws SQLException if a database error occurs.
     */
    private <T> T createBean(ResultSet rs, Class<T> type,
            PropertyDescriptor[] props, int[] columnToProperty)
            throws SQLException {

        T bean = this.newInstance(type);

        for (int i = 1; i < columnToProperty.length; i++) {     //这里的 i代表的实际是Column在ResultSet行中的位置

            if (columnToProperty[i] == PROPERTY_NOT_FOUND) {
                continue;
            }

            PropertyDescriptor prop = props[columnToProperty[i]];    
            Class<?> propType = prop.getPropertyType();

            Object value = this.processColumn(rs, i, propType);    //根据结果集、column的位置、属性类型来处理COlumn,返回数据值

            if (propType != null && value == null && propType.isPrimitive()) {        //如果返回的值为null,并且属性类型为基本数据类型,就从primitiveDefaults中获取该类型对应的默认值
                value = primitiveDefaults.get(propType);
            }

            this.callSetter(bean, prop, value); //为bean的prop属性赋value值
        }

        return bean;
    }
看下processColumn方法:该类的主要作用就是根据bean属性的类型以及对应该属性的Column返回用户期望的值

/**
     * Convert a <code>ResultSet</code> column into an object.  Simple
     * implementations could just call <code>rs.getObject(index)</code> while
     * more complex implementations could perform type manipulation to match
     * the column's type to the bean property type.
     *
     * <p>
     * This implementation calls the appropriate <code>ResultSet</code> getter
     * method for the given property type to perform the type conversion.  If
     * the property type doesn't match one of the supported
     * <code>ResultSet</code> types, <code>getObject</code> is called.
     * </p>
     *
     * @param rs The <code>ResultSet</code> currently being processed.  It is
     * positioned on a valid row before being passed into this method.
     *
     * @param index The current column index being processed.
     *
     * @param propType The bean property type that this column needs to be
     * converted into.
     *
     * @throws SQLException if a database access error occurs
     *
     * @return The object from the <code>ResultSet</code> at the given column
     * index after optional type processing or <code>null</code> if the column
     * value was SQL NULL.
     */
    protected Object processColumn(ResultSet rs, int index, Class<?> propType)
        throws SQLException {

        if ( !propType.isPrimitive() && rs.getObject(index) == null ) {       //如果属性字段的类型是基本数据类型,并且数据库中返回了null,此处也返回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);             //其他类型
        }

    }
当数据获取之后,就需要给bean的属性赋值了,该过程是通过callSetter方法实现的:

/**
     * Calls the setter method on the target object for the given property.
     * If no setter method exists for the property, this method does nothing.
     * @param target The object to set the property on.
     * @param prop The property to set.
     * @param value The value to pass into the setter.
     * @throws SQLException if an error occurs setting the property.
     */
    private void callSetter(Object target, PropertyDescriptor prop, Object value)
            throws SQLException {

        Method setter = prop.getWriteMethod();                              //获取对属性的写方法

        if (setter == null) {               //没有对应的写方法则不执行任何操作
            return;
        }

        Class<?>[] params = setter.getParameterTypes();            //获取set方法的参数类型
        try {
            // convert types for some popular ones
            if (value instanceof java.util.Date) {                   //如果processColumn方法返回的值类型为java.sql.包中的一些类型,则需要对应进行特殊处理
                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)) {
                    value = new java.sql.Timestamp(((java.util.Date) value).getTime());
                }
            }

            // Don't call setter if the value object isn't the right type
            if (this.isCompatibleType(value, params[0])) {                  //这里调用isCompatibleType方法来判断value与参数类型是否匹配,匹配则赋值,否则抛出异常
                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());
        }
    }
下面是isCompatibleType方法:

/**
     * ResultSet.getObject() returns an Integer object for an INT column.  The
     * setter method for the property might take an Integer or a primitive int.
     * This method returns true if the value can be successfully passed into
     * the setter method.  Remember, Method.invoke() handles the unwrapping
     * of Integer into an int.
     *
     * @param value The value to be passed into the setter method.
     * @param type The setter's parameter type (non-null)
     * @return boolean True if the value is compatible (null => true)
     */
    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) && Integer.class.isInstance(value)) {
            return true;

        } else if (type.equals(Long.TYPE) && Long.class.isInstance(value)) {
            return true;

        } else if (type.equals(Double.TYPE) && Double.class.isInstance(value)) {
            return true;

        } else if (type.equals(Float.TYPE) && Float.class.isInstance(value)) {
            return true;

        } else if (type.equals(Short.TYPE) && Short.class.isInstance(value)) {
            return true;

        } else if (type.equals(Byte.TYPE) && Byte.class.isInstance(value)) {
            return true;

        } else if (type.equals(Character.TYPE) && Character.class.isInstance(value)) {
            return true;

        } else if (type.equals(Boolean.TYPE) && Boolean.class.isInstance(value)) {
            return true;

        }
        return false;

    }
==========

这样一来,一个bean的创建就算完成了,我们再来看一下整个过程:

1、首先根据Bean的类型,获取该Bean的所有属性描述信息数组:PropertyDescriptor[];

2、使用前面获取的数组与数据集中的字段进行匹配,获取存放匹配信息的int数组;

3、有了匹配信息,有了数据集,有了Bean属性信息,那么就可以创建Bean了;

4、创建Bean实例(type.newInstance()),然后通过反射给相应属性赋值;

5、最后返回bean实例。

但这里还需要注意的是:数据库中值与JavaBean中值的转换:因为两者存放的数据类型不同,以及不同类型之间的转换需要特殊处理,所以就有了上面isCompatibleType和processColumn方法的出现。

====

另该类还有个toBeanList方法,就是获取多个bean实例,其本质与toBean方法是一致的,这里不做介绍。

========================================================多谢阅读,欢迎指正=============================================


转载于:https://my.oschina.net/jasonultimate/blog/166791

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值