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的转换就完成了,还是相当简洁的。