小酒一杯品原始碼-DbUtils程式碼解讀
阿新 • • 發佈:2020-08-01
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;
}
這裡ResultSetMetaData
和PropertyDescriptor
分別是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的轉換就完成了,還是相當簡潔的。
推薦:抖音如何漲粉