前几天笔者在写了一个比较简陋的底层数据库封装类, 但测试时出现NoSuchMethodException.下面是部分源代码..

 

/**
* 对数据库的查询<br/>
* 注意:使用时,表的列名必须与实体对应的属性名一致.
*
* @param sql
*            SQL语句
* @param params
*            注入参数
* @param clazz
*            对象类的模板
* @return 对象类的集合,没有结果时size()为0.
*/
public static <T> List<T> executeQuery(String sql, Object[] params,Class<T> clazz) {
// 结果集合
ArrayList<T> result = new ArrayList<T>();
// 获取Connection
Connection con = getConnection();
// 声明PreparedStatement
PreparedStatement ps = null;
// 声明结果集
ResultSet rs = null;
// 如果连接不为空
if (con != null) {
try {
// 注入SQL
ps = con.prepareStatement(sql);
// 注入参数
insertParams(ps, params);
// 获取结果集
rs = ps.executeQuery();
// 获取结果集元数据
ResultSetMetaData rsmd = rs.getMetaData();
// 获取列的信息
HashMap<String, Integer> columnInfo = getColumnInfo(rsmd);
// 获取列名
Set<String> columnNames = columnInfo.keySet();
// 循环结果集
while (rs.next()) {
// 实例化一个泛型对象
T t = clazz.newInstance();
// 定义为Object类型,以至于可以接收多种数据类型
Object value = null;
for (String columnName : columnNames) {
// 获取列的类型,调用ResultSet对应的set方法来获取value
int type = columnInfo.get(columnName);
switch (type) {
case Types.INTEGER:
value = rs.getInt(columnName);
break;
case Types.DOUBLE:// SQL中的Float与Types.Double对应
value = rs.getDouble(columnName);
break;
case Types.VARCHAR:
value = rs.getString(columnName);
break;
case Types.TIMESTAMP:
value = rs.getTimestamp(columnName);
default:
value = rs.getObject(columnName);
break;
}
String name = "set" + UpperFirstCase(columnName);
// 获取泛型实例对象的对应方法并执行
Method method = null;
method = clazz.getDeclaredMethod(name,
new Class<?>[] { value.getClass() });
method.invoke(t, value);
}
// 将此泛型实例添加到结果集合中
result.add(t);
}
} catch (SQLException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
closeAll(con, ps, rs);
}
}
return result;
}

经过调试后发现问题出现在65行的getDeclaredMethod(String,Class<?>[])方法中, 在调试过程中还发现该方法可以反射并执行getXXX()方法. 但在反射setXXX(xxx,xxxx)方法时,反射的方法名是正确的,于是把错误圈定在参数类型上. 

在一系列测试后发现, 基本数据类型存放到Object后再取出来会变成其对应的封装类, 而getDeclaredMethod(String,Class<?>[])方法又区分基本类型和其对应的封装类.

经过一翻探究后想出了三种解决方法:

  1. 使用getDeclaredMethods()方法, 通过遍历的方式对方法名进行匹配, 可在循环内部添加条件来提高匹配精度. 但效率比较低.

  2. 约定类的方法的参数类型不能使用基本类型,只能用其对应的封装类(别笑...这种方法很有效...其实我当时忍不住要笑...)

  3. (也就是我最后采取的方法). 通过以下方法, 传入Object类, 尝试获得基本类型.

    /**
    * 由于反射的getDeclaredMethod()方法不支持基本类型,所以用这个方法进行转换
    *
    * @param o
    * @return
    */
    private static Class<?> changeToBasicClass(Object o) {
    Class<?> clazz = o.getClass();
    if (clazz == Integer.class) {
    clazz = Integer.TYPE;
    } else if (clazz == Long.class) {
    clazz = Long.TYPE;
    } else if (clazz == Float.class) {
    clazz = Float.TYPE;
    } else if (clazz == Double.class) {
    clazz = Double.TYPE;
    } else if (clazz == Short.class) {
    clazz = Short.TYPE;
    } else if (clazz == Boolean.class) {
    clazz = Boolean.TYPE;
    } else if (clazz == Character.class) {
    clazz = Character.TYPE;
    } else if (clazz == Byte.class) {
    clazz = Byte.TYPE;
    }
    return clazz;
    }

    下面贴上笔者修改后的代码.

    /**
    * 对数据库的查询<br/>
    * 注意:使用时,表的列名必须与实体对应的属性名一致.
    *
    * @param sql
    *            SQL语句
    * @param params
    *            注入参数
    * @param clazz
    *            对象类的模板
    * @return 对象类的集合,没有结果时size()为0.
    */
    public static <T> List<T> executeQuery(String sql, Object[] params,
    Class<T> clazz) {
    // 结果集合
    ArrayList<T> result = new ArrayList<T>();
    // 获取Connection
    Connection con = getConnection();
    // 声明PreparedStatement
    PreparedStatement ps = null;
    // 声明结果集
    ResultSet rs = null;
    // 如果连接不为空
    if (con != null) {
    try {
    // 注入SQL
    ps = con.prepareStatement(sql);
    // 注入参数
    insertParams(ps, params);
    // 获取结果集
    rs = ps.executeQuery();
    // 获取结果集元数据
    ResultSetMetaData rsmd = rs.getMetaData();
    // 获取列的信息
    HashMap<String, Integer> columnInfo = getColumnInfo(rsmd);
    // 获取列名
    Set<String> columnNames = columnInfo.keySet();
    // 循环结果集
    while (rs.next()) {
    // 实例化一个泛型对象
    T t = clazz.newInstance();
    // 定义为Object类型,以至于可以接收多种数据类型
    Object value = null;
    for (String columnName : columnNames) {
    // 获取列的类型,调用ResultSet对应的set方法来获取value
    int type = columnInfo.get(columnName);
    switch (type) {
    case Types.INTEGER:
    value = rs.getInt(columnName);
    break;
    case Types.DOUBLE:// SQL中的Float与Types.Double对应
    value = rs.getDouble(columnName);
    break;
    case Types.VARCHAR:
    value = rs.getString(columnName);
    break;
    case Types.TIMESTAMP:
    value = rs.getTimestamp(columnName);
    default:
    value = rs.getObject(columnName);
    break;
    }
    String name = "set" + UpperFirstCase(columnName);
    // 获取泛型实例对象的对应方法并执行
    Method method = null;
    try {
    method = clazz.getDeclaredMethod(name,
    new Class<?>[] { value.getClass() });
    } catch (NoSuchMethodException e) {
    // 如果无法获取方法,可能是因为参数是基本类型,调用changeToBasicClass()方法
    try {
    method = clazz
    .getDeclaredMethod(
    name,
    new Class<?>[] { changeToBasicClass(value) });
    } catch (NoSuchMethodException e1) {
    e1.printStackTrace();
    }
    }
    method.invoke(t, value);
    }
    // 将此泛型实例添加到结果集合中
    result.add(t);
    }
    } catch (SQLException e) {
    e.printStackTrace();
    } catch (InstantiationException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    } catch (IllegalArgumentException e) {
    e.printStackTrace();
    } catch (SecurityException e) {
    e.printStackTrace();
    } catch (InvocationTargetException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    } finally {
    closeAll(con, ps, rs);
    }
    }
    return result;
    }

    不过有一点东西很蛋疼, 那就是

    public void a(int b){}
    public void a(Integer b){}

    在Java看来,这是两个方法不同的方法, 它们之间是重载关系....

还有一点小发现...自从在编程这方面发展后...记忆力越来越差了...