問題說明
接手一個新項目的時候意外發現項目一直有一個c3p0的報錯,完整信息是這樣的:
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,遇見報錯第一時間就求助搜索引擎了,沒有找到太多有用的信息,於是跟進源碼看了一下。
相關的報錯信息在NewPooledConnection.java這個類裏,拋出這個異常的代碼段如下所示:
// methods below must be called from sync'ed methods
/*
* If a throwable cause is provided, the PooledConnection is known to be broken (cause is an invalidating exception)
* and this method will not throw any exceptions, even if some resource closes fail.
*
* If cause is null, then we think the PooledConnection is healthy, and we will report (throw) an exception
* if resources unexpectedlay fail to close.
*/
private void close( Throwable cause ) throws SQLException
{
if ( this.invalidatingException == null )
{
List closeExceptions = new LinkedList();
// cleanup ResultSets
cleanupResultSets( closeExceptions );
// cleanup uncached Statements
cleanupUncachedStatements( closeExceptions );
// cleanup cached Statements
try
{ closeAllCachedStatements(); }
catch ( SQLException e )
{ closeExceptions.add(e); }
// cleanup physicalConnection
try
{ physicalConnection.close(); }
catch ( SQLException e )
{
if (logger.isLoggable( MLevel.FINER ))
logger.log( MLevel.FINER, "Failed to close physical Connection: " + physicalConnection, e );
closeExceptions.add(e);
}
// update our state to bad status and closed, and log any exceptions
if ( connection_status == ConnectionTester.CONNECTION_IS_OKAY )
connection_status = ConnectionTester.CONNECTION_IS_INVALID;
if ( cause == null )
{
this.invalidatingException = NORMAL_CLOSE_PLACEHOLDER;
if ( 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 );
}
}
}
在stackoverflow上搜索到的相關問題這麼說:
This is the code that triggers this log statement in C3P0:
if ( logger.isLoggable( MLevel.FINEST ) ) logger.log( MLevel.FINEST,
this + ” closed by a client.”,
new Exception(“DEBUG – CLOSE BY CLIENT STACK
TRACE”) );Note that:
This is not an exception, the new Exception is used merely to show
execution path for debug purposes. And yes, this is only a debug
message (actually, FINEST is the lowest possible level in
java.util.logging). To wrap this up: ignore and tune your logging
levels to skip these.
意思就是說我們不用在意這個報錯,這並不是提示我們項目有錯誤信息,只是一個提示。提高日誌級別就看不到了。
這個看上去能解決問題,但肯定不是我想要的答案,於是繼續搜索。
解決方案
網上講說,只要修改配置文件就可以解決這個問題:
找到配置文件,去掉
<!--c3p0將建一張名爲c3p0_test的空表,並使用其自帶的查詢語句進行測試。如果定義了這個參數那麼
屬性preferredTestQuery將被忽略。你不能在這張c3p0_test表上進行任何操作,它將只供c3p0測試
使用。Default: null-->
<property name="automaticTestTable"><value>c3p0_test</value></property>
增加:
<!--定義所有連接測試都執行的測試語句。在使用連接測試的情況下這個一顯著提高測試速度。注意:
測試的表必須在初始數據源的時候就存在。Default: null-->
<property name="preferredTestQuery"><value>SELECT 1 FORM TABLE</value></property>
但是我檢查了一下自己的項目,發現項目中根本沒有這兩個配置項,那這兩個配置肯定沒能解決根本問題。不過這個信息還是有價值的,對照查詢c3p0配置文件說明,發現這兩個配置文件都是testConnectionOnCheckin這個配置項使用的,也就是用來檢查數據庫連接是否正常的。有第一個配置文件的同學可以嘗試一下,未必沒用。
真實原因解析
自己最後在網上找到了一篇解答,解釋了爲什麼會拋出異常。
spring在配置數據源時,一般是這麼寫的:
<bean id="ds1" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
</bean>
中間配置數據源的相關內容,重點就在class和destory-method兩個屬性上,一個是實現類,一個是析構方法。spring讀取配置文件後,需要根據這些信息來實例化向關內,再根據配置信息構造數據源。但是由於spring本身沒辦法確認類是不是真實有效,打開和關閉連接的方法可用,那麼爲了確保這些事情,spring會去實際構造一個鏈接,打開、查詢、關閉,完成這麼一套流程,也就能夠保證這個配置是真實有效的。
我們知道任何一個數據庫連接的實現類都是在實java.sql.Connection這個接口。java是這麼規定的,接口的實現類必須實現接口的所有方法,反過來說,數據庫的實現類就有可能包含java.sql.Connection中沒有的方法。所以spring就需要destory-method這個屬性來確認到底使用什麼方法來close(關閉連接釋放資源)。
回到我們最初的問題,既然已經清楚了這個過程,那麼我們就可以確認,這個異常,其實不是一個錯誤,而是一個提示信息,產生於程序嘗試建立數據庫連接的過程,信息的拋出是爲了告訴我們連接建立成功並且測試鏈接已經成功關閉。