java.sql.Connection的close方法究竟幹了啥(以MySQL為例),connectionclose
轉載請註明出處:http://blog.csdn.net/aheeoheehahee/article/details/42641601
謹將此文送給和我一樣具有考據癖的程式員,希望能幫到大家…………
閑言少敘,上代碼。
public static void main(String[] args) throws Exception {// TODO Auto-generated method stubClass.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 exceptionSystem.out.println(conn == null); // not null after conn.close()Connection emptyConn = null;emptyConn.isClosed(); // NullPointerException}
解釋:
1. java.sql.Connection.close()方法做的是立刻釋放connection對象佔用的資料庫聯結資源,而不是等到JVM的記憶體回收機制將其回收。API上有說,連結:http://docs.oracle.com/javase/7/docs/api/java/sql/Connection.html#close%28%29 。並不是像我原以為的那樣,close方法會簡單地將conn對象設定為null。事實上,在調用close()之後,conn仍然不為null。
2. 對一個為null的connection,調用close()方法會報null 指標異常。
關於close()方法的解釋,知乎上的幾個回答使我受到一些啟發,傳送門:http://www.zhihu.com/question/20849384/answer/16382647
當然,如果你只是想知道為什麼關閉一個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; } }