前幾天筆者在寫了一個比較簡陋的底層數據庫封裝類, 但測試時出現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<?>[])方法又區分基本類型和其對應的封裝類.
經過一翻探究後想出了三種解決方法:
使用getDeclaredMethods()方法, 通過遍歷的方式對方法名進行匹配, 可在循環內部添加條件來提高匹配精度. 但效率比較低.
約定類的方法的參數類型不能使用基本類型,只能用其對應的封裝類(別笑...這種方法很有效...其實我當時忍不住要笑...)
(也就是我最後採取的方法). 通過以下方法, 傳入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看來,這是兩個方法不同的方法, 它們之間是重載關係....
還有一點小發現...自從在編程這方面發展後...記憶力越來越差了...