java.sql.Connection的close方法究竟幹了啥(以MySQL為例),connectionclose

來源:互聯網
上載者:User

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;        }    }




相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.