在我所接触的软件开发人员中,已经听到N多次数据库死锁的问题,说实在的,每次听到有人向他们报告数据库死锁的问题,我总是感到很诧异,始终怀疑他们的代码有问题。如果各种异常能得到合理的处理,会将死锁的可能性降到最低。
怎样合理地处理JDBC编程中的异常问题,好多权威的书籍也没有给出满意的答案。在2003年电子工业出版社出版的美国计算机宝典丛书《Java数据库编程宝典》一书中,在主要的章节,关闭数据库连接均在catch代码块中进行,直到最后的第24章多次出现令人质疑的同样的一段代码
在《Java2核心技术》(第7版)下卷第四章数据库编程一章中,找不到有关数据库事务编程的异常处理的代码。由此可见Java界缺乏对JDBC的应用开发进行深入的研究。
如果打开了数据库连接事务,代码可能是这样的:
JadePool在处理异常上,还是下了相当的功夫的。在核心类ProcessVO中,凡是与DML操作的相关异常,均进行了整合,统一抛出SQLException异常。比如,在实现插入的底层核心代码中,共出现了三种类型的异常。
ProcessVO中实现事务提交的代码
ProcessVO中实现事务回滚的代码
ProcessVO中关闭数据库连接或者叫会话的的代码
在ProcessVO中进一步完善了终结守卫者模式中的代码
以上这些代码,能共同保证在发生SQLException的前提下,如果打开了事务,确保事务回滚,在关闭会话前关闭事务。
在JadePool发布前,有关异常与事务的提交、回滚之间的关联做了大量的测试,能确保JadePool的核心类ProcessVO是健壮的、可靠的。
在此, 欢迎提出宝贵意见,帮助我更好的完善JadePool开源项目;更希望有实力的Java工作者参与。
怎样合理地处理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工作者参与。