我们先来看看经典的JDBC操作数据库
- 加载数据库驱动
- 创建数据库连接
- 创建一个Statement
- 执行SQL语句
- 处理返回结果
- 关闭连接
为了提高性能,利用连接池,很好的解决了获取数据库连接,复用数据库连接,但是还有执行SQL,处理返回结果还是没有抽离出来,为了让代码更好的解耦,我们来看看DBUtils是怎么做的!DBUtils主要做了两件事,一件是填充执行SQL语句参数,另外一件是处理返回结果,封装从对象实体。
只看主要代码,从QueryRunner.query()入口方法开始
private T query(Connection conn, boolean closeConn, String sql, ResultSetHandler rsh, Object... params)
throws SQLException {
//省略不重要的代码
PreparedStatement stmt = null;
ResultSet rs = null;
T result = null;
try {
stmt = this.prepareStatement(conn, sql);
//封装SQL参数
this.fillStatement(stmt, params);
//查询数据库
rs = this.wrap(stmt.executeQuery());
//封装数据库返回结果为相应的对象实体
result = rsh.handle(rs);
} catch (SQLException e) {
this.rethrow(e, sql, params);
} finally {
try {
close(rs);
} finally {
close(stmt);
if (closeConn) {
close(conn);
}
}
}
return result;
}
fillStatement方法操作逻辑如下:
- 通过Statement.getParameterMetaData() 获取SQL参数实体对象ParameterMetaData
- 赋值变量params数组,遍历顺序需要跟SQL参数顺序一致
//比如如下两个参数,需要按顺序填充
String sql = "select * from user where groupId=? and sex=?";
Object[] params = new Object[2];
params[0] = groupId;
params[1] = sex;
fillStatement方法代码如下:
public void fillStatement(PreparedStatement stmt, Object... params)
throws SQLException {
// check the parameter count, if we can
ParameterMetaData pmd = null;
if (!pmdKnownBroken) {
try {
pmd = stmt.getParameterMetaData();
if (pmd == null) { // can be returned by implementations that don't support the method
pmdKnownBroken = true;
} else {
int stmtCount = pmd.getParameterCount();
int paramsCount = params == null ? 0 : params.length;
if (stmtCount != paramsCount) {
throw new SQLException("Wrong number of parameters: expected "
+ stmtCount + ", was given " + paramsCount);
}
}
} catch (SQLFeatureNotSupportedException ex) {
pmdKnownBroken = true;
}
// TODO see DBUTILS-117: would it make sense to catch any other SQLEx types here?
}
// nothing to do here
if (params == null) {
return;
}
CallableStatement call = null;
if (stmt instanceof CallableStatement) {
call = (CallableStatement) stmt;
}
for (int i = 0; i < params.length; i++) {
if (params[i] != null) {
if (call != null && params[i] instanceof OutParameter) {
((OutParameter)params[i]).register(call, i + 1);
} else {
stmt.setObject(i + 1, params[i]);
}
} else {
// VARCHAR works with many drivers regardless
// of the actual column type. Oddly, NULL and
// OTHER don't work with Oracle's drivers.
int sqlType = Types.VARCHAR;
if (!pmdKnownBroken) {
// TODO see DBUTILS-117: does it make sense to catch SQLEx here?
try {
/*
* It's not possible for pmdKnownBroken to change from
* true to false, (once true, always true) so pmd cannot
* be null here.
*/
sqlType = pmd.getParameterType(i + 1);
} catch (SQLException e) {
pmdKnownBroken = true;
}
}
stmt.setNull(i + 1, sqlType);
}
}
}
handle方法操作逻辑如下:
拿BeanHandler来分析
public T handle(ResultSet rs) throws SQLException {
return rs.next() ? this.convert.toBean(rs, this.type) : null;
}
public T toBean(ResultSet rs, Class extends T> type) throws SQLException {
return this.convert.toBean(rs, type);
}
public T toBean(ResultSet rs, Class extends T> type) throws SQLException {
//创建返回实体对象
T bean = this.newInstance(type);
//填充返回实体对象,属性必须应有set方法
return this.populateBean(rs, bean);
}
主要看populateBean方法
public T populateBean(ResultSet rs, T bean) throws SQLException {
//获取对象的属性数组
PropertyDescriptor[] props = this.propertyDescriptors(bean.getClass());
ResultSetMetaData rsmd = rs.getMetaData();
//保存数据库返回结果字段对应实体对象属性关系
int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);
//填充实体属性
return populateBean(rs, bean, props, columnToProperty);
}
private PropertyDescriptor[] propertyDescriptors(Class> c)
throws SQLException {
// Introspector caches BeanInfo classes for better performance
BeanInfo beanInfo = null;
try {
//通过调用Introspector.getBeanInfo(c)方法可以获得BeanInfo对象,该对象封装了c类的所有属性
beanInfo = Introspector.getBeanInfo(c);
} catch (IntrospectionException e) {
throw new SQLException(
"Bean introspection failed: " + e.getMessage());
}
return beanInfo.getPropertyDescriptors();
}
PropertyDescriptor[]是什么?看看Introspector.getBeanInfo(Tickets64.class)得到什么?
public class Tickets64 {
private int id;
private String stub;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getStub() {
return stub;
}
public void setStub(String stub) {
this.stub = stub;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
看完一目了然了吧
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;
}
private T populateBean(ResultSet rs, T bean,
PropertyDescriptor[] props, int[] columnToProperty)
throws SQLException {
for (int i = 1; i < columnToProperty.length; i++) {
if (columnToProperty[i] == PROPERTY_NOT_FOUND) {
continue;
}
PropertyDescriptor prop = props[columnToProperty[i]];
Class> propType = prop.getPropertyType();
Object value = null;
if(propType != null) {
//获取数据库返回结果集中的一个字段对应的值
value = this.processColumn(rs, i, propType);
//如果数据库没有返回,则用默认值
if (value == null && propType.isPrimitive()) {
value = primitiveDefaults.get(propType);
}
}
//对应实体对象bean赋值对应属性
this.callSetter(bean, prop, value);
}
return bean;
}
protected Object processColumn(ResultSet rs, int index, Class> propType)
throws SQLException {
Object retval = rs.getObject(index);
if ( !propType.isPrimitive() && retval == null ) {
return null;
}
/**
* ServiceLoader columnHandlers = ServiceLoader.load(ColumnHandler.class);
* ServiceLoader会在包的META-INF得services文件夹下面寻找所有ColumnHandler相应类的实现
*/
for (ColumnHandler handler : columnHandlers) {
if (handler.match(propType)) {
retval = handler.apply(rs, index);
break;
}
}
return retval;
}
private void callSetter(Object target, PropertyDescriptor prop, Object value)
throws SQLException {
Method setter = getWriteMethod(target, prop, value);
if (setter == null || setter.getParameterTypes().length != 1) {
return;
}
try {
Class> firstParam = setter.getParameterTypes()[0];
/**
* ServiceLoader propertyHandlers = ServiceLoader.load(PropertyHandler.class);
* ServiceLoader会在包的META-INF得services文件夹下面寻找所有PropertyHandler相应类的实现
*/
for (PropertyHandler handler : propertyHandlers) {
//判断转换器是否可以转换
if (handler.match(firstParam, value)) {
//转换成对应的值
value = handler.apply(firstParam, value);
break;
}
}
// Don't call setter if the value object isn't the right type
if (this.isCompatibleType(value, firstParam)) {
setter.invoke(target, new Object[]{value});
} else {
throw new SQLException(
"Cannot set " + prop.getName() + ": incompatible types, cannot convert "
+ value.getClass().getName() + " to " + firstParam.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());
}
}
看其中一个转换器IntegerColumnHandler 实现
public class IntegerColumnHandler implements ColumnHandler {
@Override
public boolean match(Class> propType) {
return propType.equals(Integer.TYPE) || propType.equals(Integer.class);
}
@Override
public Object apply(ResultSet rs, int columnIndex) throws SQLException {
return Integer.valueOf(rs.getInt(columnIndex));
}
}
来看看META-INF.services下org.apache.commons.dbutils.ColumnHandler文件
org.apache.commons.dbutils.handlers.columns.StringColumnHandler
org.apache.commons.dbutils.handlers.columns.IntegerColumnHandler
org.apache.commons.dbutils.handlers.columns.BooleanColumnHandler
org.apache.commons.dbutils.handlers.columns.LongColumnHandler
org.apache.commons.dbutils.handlers.columns.DoubleColumnHandler
org.apache.commons.dbutils.handlers.columns.FloatColumnHandler
org.apache.commons.dbutils.handlers.columns.ShortColumnHandler
org.apache.commons.dbutils.handlers.columns.ByteColumnHandler
org.apache.commons.dbutils.handlers.columns.TimestampColumnHandler
org.apache.commons.dbutils.handlers.columns.SQLXMLColumnHandler
至此,DBUtils源码走完了。