谨将此文送给和我一样具有考据癖的程序员,希望能帮到大家…………
闲言少叙,上代码。
public static void main(String[] args) throws Exception { // TODO Auto-generated method stub Class.forName("com.mysql.jdbc.Driver"); Connection conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/financial_db", "root", "admin"); conn.close(); System.out.println(conn.isClosed()); // true after conn.close() if no exception System.out.println(conn == null); // not null after conn.close() Connection emptyConn = null; emptyConn.isClosed(); // NullPointerException }
解释:
1. java.sql.Connection.close()方法做的是立刻释放connection对象占用的数据库联接资源,而不是等到JVM的垃圾回收机制将其回收。并不是像我原以为的那样,close方法会简单地将conn对象设置为null。事实上,在调用close()之后,conn仍然不为null。
2. 对一个为null的connection,调用close()方法会报空指针异常。
当然,如果你只是想知道为什么关闭一个Connection要用close方法而不是直接置为null,或者等待其被垃圾回收,那么看到这里你就可以关闭窗口了。如果你和我一样,对这个close方法究竟干了啥感兴趣,那么请不厌其烦接着往下看。
这个close方法究竟干了啥,好,读了MySQL Connector/J 的代码,接着写:
java.sql.Connection自身是一个接口,具体怎么实现是由JDBC自己实现的。MySQL中的实现是
1. 先用com.mysql.jdbc.Connection接口来继承java.sql.Connection接口:
public interface Connection extends java.sql.Connection, ConnectionProperties
2. 再用com.mysql.jdbc.MySQLConnection继承com.mysql.jdbc.Connection接口:
public interface MySQLConnection extends Connection, ConnectionProperties
3.最后使用com.mysql.jdbc.ConnectionImpl来实现com.mysql.jdbc.MySQLConnection接口:
public class ConnectionImpl extends ConnectionPropertiesImpl implements MySQLConnection
此处猜想,如果使用MySQL Connector/J来联接MySQL数据库,通过java.sql.DriverManager.getConnection(...)方法获得的Connection对象其类型应该是com.mysql.jdbc.ConnectionImpl(准确地说应该是其本身或其子类). 代码证明:
public static void main(String[] args) throws Exception { Class.forName("com.mysql.jdbc.Driver"); Connection conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/financial_db", "root", "admin"); System.out.println(conn instanceof com.mysql.jdbc.ConnectionImpl); // true }结果为true,假设成立。
找到com.mysql.jdbc.ConnectionImpl的close方法,我们编程中使用的java.sql.Connection.close()方法其在MySQL Connector/J的实现就是该方法,如下:
/** * In some cases, it is desirable to immediately release a Connection's * database and JDBC resources instead of waiting for them to be * automatically released (cant think why off the top of my head) <B>Note:</B> * A Connection is automatically closed when it is garbage collected. * Certain fatal errors also result in a closed connection. * * @exception SQLException * if a database access error occurs */ public void close() throws SQLException { synchronized (getConnectionMutex()) { if (this.connectionLifecycleInterceptors != null) { new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) { @Override void forEach(Extension each) throws SQLException { ((ConnectionLifecycleInterceptor) each).close(); } }.doForAll(); } realClose(true, true, false, null); } }
官方的注释说,这个方法用在那些需要立即释放连接资源的情况下。一个连接在被垃圾回收的时候是自动关闭的,并且一些致命错误(fatal errors)也会导致这个连接关闭。
通过代码可以发现,close这个方法里面,真正释放连接资源的是最下面这个realClose方法(上面的ConnectionLifecycleInterceptor在jdbc的代码里只找到一个接口,貌似具体的实现和MySQL的附件有关),原文这么说:Implementors of this interface can be installed via the "connectionLifecycleInterceptors" configuration property and receive events and alter behavior of "lifecycle" methods on our connection implementation. (这个接口的实现可以通过connectionLifecycleInterceptors configuration property来安装,接收时间并且改变我们connection实现当中的lifecycle方法的行为)。这不是重点,重点是下面这个realClose。代码太长,我贴在最后。
通过阅读realClose的代码,可以发现这个方法有四个参数,前三个都是boolean类型,第四个是Throwable类型。calledExplicitly为这个方法是否是从close()方法调用的,issueRollback表示在释放资源的时候是否需要回滚操作rollback()。后两个参数没有给出注释,只好顾名思义,skipLocalTeardown表示是否跳过local teardown(在google上没有搜到什么有价值的结果……),reason最终在代码中被赋值给ConnectionImpl的forceClosedReason(Why was this connection implicitly closed, if known? (for diagnostics) 为什么这个连接是隐式关闭的,如果原因可知(诊断用))。这两个参数和我们研究的问题关系不大。realClose核心代码在此:
try { if (!skipLocalTeardown) { // part 1 starts // ...... try { <strong>closeAllOpenStatements();</strong> } catch (SQLException ex) { sqlEx = ex; } // part 1 ends if (this.io != null) { // part 2 starts try { <strong>this.io.quit();</strong> } catch (Exception e) { } } } else { <strong>this.io.forceClose();</strong> } // part 2 ends if (this.statementInterceptors != null) { // part 3 starts for (int i = 0; i < this.statementInterceptors.size(); i++) { this.statementInterceptors.get(i).destroy(); } } if (this.exceptionInterceptor != null) { this.exceptionInterceptor.destroy(); } // part 3 ends } finally { // part 4 starts this.openStatements = null; if (this.io != null) { <strong>this.io.releaseResources();</strong> this.io = null; } this.statementInterceptors = null; this.exceptionInterceptor = null; <strong>ProfilerEventHandlerFactory.removeInstance(this);</strong> synchronized (getConnectionMutex()) { if (this.cancelTimer != null) { <strong>this.cancelTimer.cancel();</strong> } } <strong>this.isClosed = true;</strong> // part 4 ends } }在代码中做了注释,
part 1 通过closeAllIOpenStatements方法关闭了该连接中所有处于打开状态的声明statement,
part 2 用来关闭连接的IO(通过quit或forceClose方法),
part 3 用来销毁(destroy)连接的声明拦截器(statement interceptor)和异常拦截器(exception interceptor),
part 4 将上述已经关闭的openStatements, IO (如果IO不为null,则通过releaseResources方法释放资源), statementInterceptors和exceptionInterceptor置为null,并删除掉这个连接对应的的ProfilerEventHandler(这个类在jdbc的源码里就是孤零零一个接口,没有对应注释),将连接的cancelTimer计时器取消掉,再将连接的isClosed属性设置为true(代码中看到的isClosed方法就是返回这个属性的值)。
所以,通过这些代码,我们大概可以看出MySQL JDBC在建立一个连接的时候需要申请使用什么样的资源,至于这些资源有什么用,我再研究研究,后面的文章里再接着跟大家唠。^_^
写到这里才发现自己蠢哭了T_T,如果close方法只是将Connection置为null,那么怎么调用isClosed方法呢?唉,too young too naive,写了这么半天才转过弯来。 T_T
不过通过阅读代码,发现realclose方法将释放后的资源对象的引用都置为了null,这是一个启发,以后在对connection调用完close方法之后,再将其设置为null,免得写着写着就忘了这连接已经被关闭不能使用了(因为会直接NullPointerException),也算是减少潜在的bug风险。总之,读一趟代码下来,收获还是挺大的。听周围很多外国同行说,我们国内的程序员不愿意和大家分享自己的收获和所得,听着惭愧啊。好,这边一点了都,写到这里,感谢一下中间给我递雪糕的室友,我们下篇文章再见。Cya.
附com.jdbc.mysql.ConnectionImpl.realClose方法完整源码,感兴趣大家可以一起研究,哈哈:
/** * Closes connection and frees resources. * * @param calledExplicitly * is this being called from close() * @param issueRollback * should a rollback() be issued? * @throws SQLException * if an error occurs */ public void realClose(boolean calledExplicitly, boolean issueRollback, boolean skipLocalTeardown, Throwable reason) throws SQLException { SQLException sqlEx = null; if (this.isClosed()) { return; } this.forceClosedReason = reason; try { if (!skipLocalTeardown) { if (!getAutoCommit() && issueRollback) { try { rollback(); } catch (SQLException ex) { sqlEx = ex; } } reportMetrics(); if (getUseUsageAdvisor()) { if (!calledExplicitly) { String message = "Connection implicitly closed by Driver. You should call Connection.close() from your code to free resources more efficiently and avoid resource leaks."; this.eventSink.consumeEvent(new ProfilerEvent(ProfilerEvent.TYPE_WARN, "", this.getCatalog(), this.getId(), -1, -1, System .currentTimeMillis(), 0, Constants.MILLIS_I18N, null, this.pointOfOrigin, message)); } long connectionLifeTime = System.currentTimeMillis() - this.connectionCreationTimeMillis; if (connectionLifeTime < 500) { String message = "Connection lifetime of < .5 seconds. You might be un-necessarily creating short-lived connections and should investigate connection pooling to be more efficient."; this.eventSink.consumeEvent(new ProfilerEvent(ProfilerEvent.TYPE_WARN, "", this.getCatalog(), this.getId(), -1, -1, System .currentTimeMillis(), 0, Constants.MILLIS_I18N, null, this.pointOfOrigin, message)); } } try { closeAllOpenStatements(); } catch (SQLException ex) { sqlEx = ex; } if (this.io != null) { try { this.io.quit(); } catch (Exception e) { } } } else { this.io.forceClose(); } if (this.statementInterceptors != null) { for (int i = 0; i < this.statementInterceptors.size(); i++) { this.statementInterceptors.get(i).destroy(); } } if (this.exceptionInterceptor != null) { this.exceptionInterceptor.destroy(); } } finally { this.openStatements = null; if (this.io != null) { this.io.releaseResources(); this.io = null; } this.statementInterceptors = null; this.exceptionInterceptor = null; ProfilerEventHandlerFactory.removeInstance(this); synchronized (getConnectionMutex()) { if (this.cancelTimer != null) { this.cancelTimer.cancel(); } } this.isClosed = true; } if (sqlEx != null) { throw sqlEx; } }