问题说明
接手一个新项目的时候意外发现项目一直有一个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(关闭连接释放资源)。
回到我们最初的问题,既然已经清楚了这个过程,那么我们就可以确认,这个异常,其实不是一个错误,而是一个提示信息,产生于程序尝试建立数据库连接的过程,信息的抛出是为了告诉我们连接建立成功并且测试链接已经成功关闭。