怎樣合理地處理JDBC編程中的異常問題,好多權威的書籍也沒有給出滿意的答案。在2003年電子工業出版社出版的美國計算機寶典叢書《Java數據庫編程寶典》一書中,在主要的章節,關閉數據庫連接均在catch代碼塊中進行,直到最後的第24章多次出現令人質疑的同樣的一段代碼
finally {
if (con != null) {
con.close();
}
if (stmt != null) {
stmt.close();
}
}
在這段代碼中,關閉資源時,出現了次序錯誤在《Java2核心技術》(第7版)下卷第四章數據庫編程一章中,找不到有關數據庫事務編程的異常處理的代碼。由此可見Java界缺乏對JDBC的應用開發進行深入的研究。
而有關多個異常的處理的代碼絕大多數是按照下面的結構實現
try {
} catch (SQLException e) {
} catch (IOException e) {
} catch (XXXException e) {
} finally {
}
如果沒有打開數據庫連接事務,代碼可能是這樣的:
try {
...
} catch (SQLException e) {
} catch (IOException e) {
} catch (XXXException e) {
} finally {
con.close()
}
在這種情況下,數據庫發生死鎖的可能性爲零。如果打開了數據庫連接事務,代碼可能是這樣的:
try {
...
con.commit();
} catch (SQLException e) {
con.rollback();
} catch (IOException e) {
} catch (XXXException e) {
} finally {
con.close();
}
在這樣混合了多種異常的代碼中,如果發生了非SQLException異常,極有可能出現死鎖的情況,一旦發生非SQLException異常,事務既沒有提交,也沒有回滾,雖然JDBC關閉了數據庫連接,但是在數據庫服務器中,數據庫事務仍然是打開的。在使用SQL Server2000時,這一點可以通過對SQL Server2000的企業管理器/管理/當前活動/鎖/對象等部分觀察而得到結論。其它的數據庫產品是否也有類似的情況?這段代碼看似優雅,但缺乏健壯性;從代碼的健壯性考慮,應當將代碼拆分成三個try{}catch(XXXException
e){}finally{}。如果希望維持代碼的優雅,則應當在這段代碼的實現細節上多下點功夫,比如多增加幾個boolean變量,看代碼能否執行到下一個階段。JadePool在處理異常上,還是下了相當的功夫的。在覈心類ProcessVO中,凡是與DML操作的相關異常,均進行了整合,統一拋出SQLException異常。比如,在實現插入的底層核心代碼中,共出現了三種類型的異常。
try{
...
} catch (java.lang.ClassCastException ex) {
throw new SQLException("java.lang.ClassCastException: " + ex.getMessage(), ex.getCause());
} catch (NumberFormatException ex) {
throw new SQLException("NumberFormatException: " + ex.getMessage(), ex.getCause());
} catch (IOException ex) {
throw new SQLException("IOException: " + ex.getMessage(), ex.getCause());
} finally {
pstmt.close();
}
相關的資源打開和關閉,分散在整個ProcessVO的多個方法中,但在關閉資源時,嚴格注意次序。對於數據庫連接(或者叫會話)、事務、PreparedStatement對象、Statement對象、ResultSet對象等資源,ProcessVO中遵守的次序是:先打開,後關閉,後打開,先關閉。ProcessVO中實現事務提交的代碼
/**
* 提交事務。
*
* @throws SQLException
*/
public void commit() throws SQLException {
if (!con.isClosed()) {
if (!autoCommit) {
try {
con.commit();
autoCommit = true;
con.setAutoCommit(autoCommit);
failCommit = false;
con.close();
} finally {
if (failCommit) {
rollback();
}
}
}
}
}
ProcessVO中實現事務回滾的代碼
/**
* 事務回滾。
*
* @throws SQLException
*/
public void rollback() throws SQLException {
if (!con.isClosed()) {
if (!this.autoCommit) {
if (failCommit) {
if (savepoint == null) {
con.rollback();
con.close();
System.out.println("[" + dt.dateTime() + "] SQLServerException. Connection is already rollback and closed .");
} else {
con.rollback(savepoint);
con.close();
System.out.println("[" + dt.dateTime() + "] SQLServerException. Connection is already rollback at " + savepoint + " point and closed .");
}
autoCommit = true;
}
}
}
}
ProcessVO中關閉數據庫連接或者叫會話的的代碼
/**
* 關閉數據庫聯接
*
* @throws SQLException
*/
@Override
public void closeCon() throws SQLException {
try {
/*
* if (rs != null && !rs.isClosed()) { rs.close(); }
*
* if (stmt != null && !stmt.isClosed()) { stmt.close(); }
*/
} finally {
if (con != null && !con.isClosed()) {
con.close();
}
}
}
在ProcessVO中進一步完善了終結守衛者模式中的代碼
/**
* 終結守衛者,最後一道安全屏障,關閉聯接。開發人員不應依賴此方法來關閉聯接,但以下情況發生後,可能造成數據庫連接不能關閉,因此有必要保留終結守衛者模式。長期的實踐證明,終結守衛者能起到良好的作用。<br/>
* 1、網絡連接終止;<br/> 2、應用程序中發生非SQLException異常後,可能導致數據庫連接沒有關閉。<br/>
*/
private final Object _finalizerGuardian = new Object() {
@Override
protected void finalize() throws SQLException, Throwable {
if (con != null && !con.isClosed()) {
try {
super.finalize();
} finally {
if (!autoCommit) {
if (failCommit) {
try {
if (savepoint == null) {
con.rollback();
} else {
con.rollback(savepoint);
}
System.out.println("[" + dt.dateTime() + "] Connection rollback in finalize.");
} catch (SQLException ex) {
Logger.getLogger(ProcessVO.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
} finally {
con.close();
autoCommit = true;
System.out.println("[" + dt.dateTime() + "] Connection is not autocommit, rollback and then closed in finalize.");
}
} else {
con.close();
System.out.println("[" + dt.dateTime() + "] Connection is autocommit, closed in finalize,You should close it.");
}
} else {
con.close();
System.out.println("[" + dt.dateTime() + "] Connection closed in finalize,You should close it.");
}
}
}
}
};
以上這些代碼,能共同保證在發生SQLException的前提下,如果打開了事務,確保事務回滾,在關閉會話前關閉事務。
在JadePool發佈前,有關異常與事務的提交、回滾之間的關聯做了大量的測試,能確保JadePool的核心類ProcessVO是健壯的、可靠的。
在此,歡迎提出寶貴意見,幫助我更好的完善JadePool開源項目;更希望有實力的Java工作者參與。