DbUtils源碼閱讀二:DbUtils與QueryLoader

1 DbUtils


1.1 DbUtils類
DbUtils類是一個final類,提供了關於JDBC操做的靜態方法,如關閉數據庫資源,加載數據庫驅動,事務操做和打印信息。工具類是不該該有public或default構造方法,在DbUtils中卻提供了:
public DbUtils() {}
說是爲了向老版本兼容。

1.2 數據庫資源操做
1.2.1 關閉數據庫資源的操做分爲兩種:一種向上拋出異常,另外一種是捕獲異常後不作處理。以Connection關閉爲例: 
public static void close(Connection conn) throws SQLException {
    if (conn != null) { // 先判斷是否爲null,防止拋出空指針異常。
        conn.close();
    }
}
public static void closeQuietly(Connection conn) {
    try {
        close(conn); // 調用會拋異常的方法
    } catch (SQLException e) {
        // 不對異常進行處理。
    }
}
1.2.2 一塊兒關閉Connection、Statement和ResultSet資源
public static void closeQuietly(Connection conn, Statement stmt, ResultSet rs) {
        // try-finally結構,依次關閉rs, stmt, conn。
    	try {
            closeQuietly(rs);
        } finally {
            try {
                closeQuietly(stmt);
            } finally {
                closeQuietly(conn);
            }
        }
    }
代碼中使用到了嵌套式的try-finally語句塊,保證了Connection等資源將可以獲得關閉。 建議獨立使用try-catch和try-finally語句塊。
以IO流操做爲例,
1) 不獨立使用的狀況下
InputStream is = ...;
try {
    is.available();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (is != null) {
        try {
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
2) 獨立使用的狀況下
InputStream is = ...;
try {
    try {
        is.available();
    } finally {
        if (is != null) {
            is.close();
        }
    }
} catch (IOException e) {
    e.printStackTrace();
}
前者,在finally語句塊中,當調用is.close()須要另一個try-catch來處理異常。然後者的finally語句塊中, 調用is.close()所拋出的異常將被外層的try-catch來處理。而且,外層try語句塊確保關閉輸入流,而外層try語句塊將報告所出現的異常,也包括了finally子句中出現的。

1.3 事務操做
1.3.1 提交事務操做
public static void commitAndClose(Connection conn) throws SQLException {
    if (conn != null) {
        try {
            conn.commit();
        } finally { // 不管commit是否成功,都將關閉鏈接。
            conn.close();
        }
    }
}
1.3.2 回滾事務操做
public static void rollback(Connection conn) throws SQLException {
    if (conn != null) {
        conn.rollback();
    }
}

public static void rollbackAndClose(Connection conn) throws SQLException {
    if (conn != null) {
        try {
            conn.rollback();
        } finally {
            conn.close();
        }
    }
}
都判斷了傳入進來的Connection是否爲空,而後在調用Connection對象的commit()或rollback()方法,選擇使用try-finally語句塊關閉Connection資源,並向上拋出異常。關於事務還有一組對捕獲的異常不作處理的操做,如:
public static void rollbackAndCloseQuietly(Connection conn) {
    try {
        rollbackAndClose(conn);
    } catch (SQLException e) {
        // 不作處理
    }
}
1.4 加載數據庫驅動
public static boolean loadDriver(String driverClassName) {
    // 使用加載DbUtils類的類加載器。
    return loadDriver(DbUtils.class.getClassLoader(), driverClassName);
}
// 提供一個類加載器
public static boolean loadDriver(ClassLoader classLoader, String driverClassName) {
    try {
        classLoader.loadClass(driverClassName).newInstance(); // 建立Class對象所對應的類的一個實例。
        return true;
    } catch (IllegalAccessException e) {
        // 若沒有默認的構造方法,仍是適用於DriverManager的約定,如:Class.forName(driverClassName);因此返回true。
        return true;
    } catch (Exception e) {
        return false;
    }
}
1.5 打印棧信息
1.5.1 打印異常棧信息
public static void printStackTrace(SQLException e) {
    printStackTrace(e, new PrintWriter(System.err)); // 使用System.err做爲目的地,並將其包裝成一個PrintWriter。
}

public static void printStackTrace(SQLException e, PrintWriter pw) {
    SQLException next = e; // 爲傳入進來的異常對象取個別名,使其適用於下面的while循環。
    while (next != null) { // while循環的終止條件爲next == null。
        next.printStackTrace(pw); // 將堆棧信息打印到pw。
        next = next.getNextException(); // 獲取當前異常對象的下一個異常對象
        if (next != null) {
            pw.println("Next SQLException:"); // 輸出Tip
        }
    }
}
1.5.2 打印警告信息
因爲SQLWarning是SQLException的子類,因此調用了 printStackTrace方法
public static void printWarnings(Connection conn) {
    printWarnings(conn, new PrintWriter(System.err));
}

public static void printWarnings(Connection conn, PrintWriter pw) {
    if (conn != null) {
        try {
            printStackTrace(conn.getWarnings(), pw);
        } catch (SQLException e) {
            printStackTrace(e, pw); // 打印conn.getWarnings()拋出的異常棧信息
        }
    }
}

2 QueryLoader

該類負責將SQL語句註冊到內存中,並保證不會重複註冊相同SQL語句。
該類實現從.properties文件中加載SQL語句並映射到Map中。
該類提供了protected的構造方法和protected的loadQueries(String path)方法,因此能夠從該類擴展出不一樣的實現。
protected QueryLoader() {}
...
protected Map<String, String> loadQueries(String path) throws IOException {...}
2.1 餓漢式單例類
private static final QueryLoader instance = new QueryLoader();

public static QueryLoader instance() {
    return instance;
}
2.2 存放SQL語句塊的Map類型final成員屬性
private final Map<String, Map<String, String>> queries = new HashMap<String, Map<String, String>>();
外層Map的key是存放文件的路徑,而value是內層的Map,內層Map存放每一條SQL語句的key和value。Map能夠保證key是惟一的。

2.3 默認實現
protected Map<String, String> loadQueries(String path) throws IOException {
    // 將classpath中的資源文件讀入到輸入流對象中。路徑字符串格式應該以"/"打頭
    InputStream in = getClass().getResourceAsStream(path);
    if (in == null) {
        throw new IllegalArgumentException(path + " not found.");
    }
    Properties props = new Properties();
    try {
        props.load(in);
    } finally {
        in.close();
    }
    // 將Properties對象直接拷貝給HashMap,爲了更好的性能保證。
    @SuppressWarnings({ "rawtypes", "unchecked" })
    HashMap<String, String> hashMap = new HashMap(props); // loadQueries()的每一次調用都會建立出一個新的HashMap對象。
    return hashMap;
}
2.4 加載
public synchronized Map<String, String> load(String path) throws IOException {
    Map<String, String> queryMap = this.queries.get(path); // 試着到成員屬性queries中取
    // 判斷是否已經存在。
    if (queryMap == null) {
        queryMap = this.loadQueries(path); // 使用默認的實現將SQL語句加載到Map中
        this.queries.put(path, queryMap); // 放入queries中
    }
    return queryMap;
}
2.5 卸載
public synchronized void unload(String path) {
    this.queries.remove(path); // 從成員屬性queries中刪除便可
}
加載與卸載方法都加上了synchronized關鍵字,適應了多線程環境,避免被重複加載或卸載。
相關文章
相關標籤/搜索