出处:https://my.oschina.net/flashsword/blog/160498
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。
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方法里有一些关于基本类型和装箱类型的转换。
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的转换就完成了,还是相当简洁的。