1. 程式人生 > 實用技巧 >小酒一杯品原始碼-DbUtils程式碼解讀

小酒一杯品原始碼-DbUtils程式碼解讀

ORM一直是Web開發一個熱點話題,DbUtils則是給出了一個相當簡潔的答案。DbUtils的巢狀也不深,而且主動的API呼叫也非常符合程式設計師的思維(Hibernate和iBatis這種隱藏了大多數細節的框架,連找到個入口都要費半天勁)。

話說最常用的CRUD,使用JDBC最痛的無非是將ResultSet轉換為JavaBean。DbUtils則是正好命中這個要害,使用ResultSetHandler機制來解決這個問題。

之前用過Spring JDBC Template,差不多也是這個機制。DbUtils的亮點則是BeanHandler,可以無需手寫轉換函式,自動根據class生成一個handler。

BeanProcessor的核心程式碼做了幾件事:

  • 提取Bean的欄位資訊,結果集的欄位資訊,並作對映;

  • 對Bean的欄位做型別轉換

欄位對映的程式碼:

    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())) {
                    columnToProperty[col] = i;
                    break;
                }
            }
        }

        return columnToProperty;
    }

這裡ResultSetMetaDataPropertyDescriptor分別是JDBC和Bean API裡獲取的元資訊。

對欄位做型別轉換的程式碼比較多,主要方法是callSetter

<!-- lang: java -->
private void callSetter(Object target, PropertyDescriptor prop, Object value)
        throws SQLException {

    Method setter = prop.getWriteMethod();

    if (setter == null) {
        return;
    }

    Class<?>[] params = setter.getParameterTypes();
    // 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)) {
            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])) {
        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
    }

}

這裡省略了一些異常的捕獲。this.isCompatibleType方法裡有一些關於基本型別和裝箱型別的轉換。

<!-- lang: java -->
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;

}

至此,一個ResultSet至Bean的轉換就完成了,還是相當簡潔的。

推薦:抖音如何漲粉