關於CLOSE BY CLIENT STACK TRACE
程序正常運行,數據庫連接可以獲取,一些列操作都可以實現,可在debug信息中總會一段時間就報如下錯誤:
java.lang.Exception : DEBUG -- CLOSE BY CLIENT STACK TRACE
at com.mchange.v2.c3p0.impl.NewPooledConnection.close(NewPooledConnection.java:566 )
at com.mchange.v2.c3p0.impl.NewPooledConnection.close(NewPooledConnection.java:234 )
at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool$1PooledConnectionResourcePoolManager.destroyResource(C3P0PooledConnectionPool.java:470 )
at com.mchange.v2.resourcepool.BasicResourcePool$1DestroyResourceTask.run(BasicResourcePool.java:964 )
at com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread.run(ThreadPoolAsynchronousRunner.java:547 )
跟蹤錯誤代碼,發現 是 c3p0 內部的異常輸出(紅色部分 )
C3p0 相關源碼 :
com.mchange.v2.c3p0.impl. NewPooledConnection
public synchronized void close() throws SQLException
{ close( null ); }
private void close( Throwable cause ) throws SQLException
{ close( cause, false ); }
private void close( Throwable cause, boolean forced ) throws SQLException
{
。。。。。。。。。。。。。。。。
if ( cause == null )
{
this .invalidatingException = NORMAL_CLOSE_PLACEHOLDER ;
if ( Debug.DEBUG && logger .isLoggable( MLevel.FINEST ) )
logger .log( MLevel.FINEST , this + " closed by a client.", new Exception("DEBUG -- CLOSE BY CLIENT STACK TRACE") );
logCloseExceptions ( null , closeExceptions );
if (closeExceptions.size() > 0)
throw new SQLException("Some resources failed to close properly while closing " + this );
}
else
{
this .invalidatingException = cause;
if (Debug.TRACE >= Debug.TRACE_MED )
logCloseExceptions ( cause, closeExceptions );
else
logCloseExceptions ( cause, null );
}
}
}
爲何會有這樣的錯誤,一頭霧水。
看了下配置文件,覺得可能是automaticTestTable配置引起的,將其註釋掉,再運行,發現錯誤不再出現。
看了下關於 automaticTestTable的作用:
由於Mysql服務器默認的wait_timeout是8小時,也就是說一個connection空閒超過8個小時,
Mysql將自動斷開該 connection。然而在C3P0 pools中的connections如果空閒超過8小時,
Mysql將其斷開,而C3P0並不知道該connection已經失效,如果這時有 Client請求connection,
C3P0將該失效的Connection提供給Client,將會造成異常。
解決方法可爲:1.jdbcUrl上面加一個autoReconnect=true
2.由於autoReconnect=true 在新的connector/J版本里面已經deprecated了,文檔裏面建議不要使用。
所以配置參數idleConnectionTestPeriod,automaticTestTable
而配置 automaticTestTable卻 會報CLOSE BY CLIENT STACK TRACE,故選擇配置preferredTestQuery 而不是 automaticTestTable 。
以上只是在使用c3p0是發現的問題,不知道是否理解正確,不過其正真原因到目前還是弄不清楚,望解答!
補充:今天在http://hi.baidu.com/xhr8334/blog/item/cf15d1a6deb235fc9052ee9b.html看到一個見解:(引用主題內容)
我看到那個DEBUG,我說,是調試信息,修改一下LOG4J的等級就行了。
這個羣友很不解的問,既然成功了,幹嘛還要丟異常出來?
這裏就不得不說到兩個商業開發的原則問題了。
第一,對上家傳入數據嚴加過濾,對傳出給下家的數據仔細檢查。
第二,合理使用異常。
第一點其實很簡單的。也就是模塊化開發的一個思想問題。對自己的行爲負責。前端返回的數據究竟是什麼,需要進行校驗。不合格的剔除或者是修正。合格的處理完後,在傳出之前也要加以校驗,是否合格。
具體到這個問題裏,就是來自Spring關於數據源的那個配置文件。
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
//略去數據源相關信息的配置
</bean>
這個配置文件裏,有兩個信息是很管用的。一個是class,一個是destroy-method。簡單點說,一個是數據源的實現類,一個是析構方法。Spring在讀取這個配置文件以後,需要根據這些信息來實例化一些類,然後內部再根據中間的那些配置信息來實際構造數據源。比如username啥的。
可是來了個問題。不能保證這裏的ComboPooledDataSource數據源一定是可用的,也不能保證close方法一定能關閉連接,對吧?Spring本身不能檢查這個類是否真實有效,毫無Bug。實際上呢,也檢查不了。同樣的,close方法是否有效,也需要進行檢查。這就是我剛纔說的,對上家數據的嚴加檢查。
那好吧,怎麼檢查呢?最簡潔的方法莫過於實際構造一下,連接池,獲取數據庫連接,執行一個測試語句,然後關閉連接。如果一切都成功,那就OK。關於那個測試語句,配置過WebSphere數據源的同學們還記得不?有個SQL語句會默認的寫在數據源配置裏,是 " SELECT 1 FROM TABLE "。嗯,對的,這個就是測試語句。
這一套流程能走得通,走的順,那麼就可以在自己能力範圍內說這個數據源和連接池是能用的,對吧?
這裏補充一個知識。java.sql.Connection,這玩意不是class,是interface。
聲明是:public interface Connection extends Wrapper 。
任何一個JDBC數據庫連接的實現類都應該實現這個接口的全部方法。比如,close。API裏的描述是,立即釋放此
Connection
對象的數據庫和 JDBC 資源,而不是等待它們被自動釋放。
熟悉Java的同學們應該記得一點,在規範裏有個要求,就是接口的實現類必須實現接口的所有方法。但是呢,這句話還有個意思,那就是,你可以在實現所有方法之外,再寫幾個方法。沒人會管你。
啊哈,那就有疑問了。雖然API規定了close是關閉連接釋放資源的。但這只是你接口的一廂情願。也許人家實現廠家覺得close方法不夠帥,要改成closeConnection。那。。。Spring總不好傻傻的去死扣close方法來關閉連接吧?雖然這方法必須實現,但是可沒說一定要有內容啊。如果是空方法呢?
所以有了destroy-method這個配置項的出現。Spring說,不礙的,您老人家看哪個爽,告訴我就行。
現在測試完了。一切都成功了。
現在來看看第二個問題。合理使用異常。
又遇到一個問題。既然測試成功了,那總得給用戶一點交待吧?難道說,測試成功了,就悶聲大發財了?顯然不合適嘛。可以試想一下,你是程序員,然後點了個按鈕,測試。結果呢,實際上是測試成功了,但是系統啥動靜都不給你。然後你傻傻的等癡癡的盼,一直等到天荒地老……嗯嗯,扯的有點遠。如果你等一個小時還不見動靜,活不見人死不見屍的,你說你會不會罵娘?
那麼怎麼通知才能保證一定有效呢?println?這個不見得一定能看到。因爲別人也可能在同時輸出信息,一下就刷掉了。那麼有同學說了,最好是能暫停一下,我輸出以後,就暫停了,不動了。
嗯,很好。
大家想想看,輸出一大堆東西,然後此程序不動了,不繼續執行了,這是啥玩意?
這不就是異常嘛!
只有異常能保證程序員一定能看到這個信息,比如,測試成功。這就是爲什麼Spring要採用這種方式來通知的原因。
這裏呢,我想更正同學們一個習慣成自然的想法。異常不一定是通知壞消息的。異常就是異常,只要你願意,你甚至可以在代碼執行成功的時候,throws一個Exception。異常只不過是比較激烈的一種通知方式而已。無他,僅此而已。
現在又有個問題來了。既然要測試,而且每次執行到此處的時候都要測試一下。那麼……難道都卡在這裏不走了啊?顯然更不合適啊。
熟悉log4J的同學應該看出來了,這是log4J輸出的日誌。很明顯的,這種日誌只應當在開發期間存在,不應該在發佈期間存在。因爲開發期間數據庫變動很大,比如改表啊,改數據庫配置啊。所以需要通知用戶是否成功。但是產品一旦開發完畢,正式發佈,這種信息就不應再出現,因爲商業化運作的應用不允許亂動配置的,對不?
所以log4J提供了一種方法。消息級別。INFO的時候,是看不到這個異常的。實現起來也很好辦,catch了,然後不做任何處理,也就是空的catch塊。
具體實現的時候可以在catch裏判斷一下,如果等級是INFO的話,就不做任何事。如果不是,那就按照規則去做。
結合到 “合理使用異常” 這句話來說呢,就是說,需要拋出異常的時候,就拋出。不需要拋出的時候,就不拋出。對程序員來說,在必要的時候看到一串異常信息,是最合適的事情了。
關於異常的使用,這裏不展開說了。有興趣的同學可以參見林銳博士的 高質量Java編程。