- 问题的现象,线上的系统莫明的报错
### Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLException: Data source is closed
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:73) ~[mybatis-spring-1.1.1.jar:1.1.1]
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:365) ~[mybatis-spring-1.1.1.jar:1.1.1]
at com.sun.proxy.$Proxy71.update(Unknown Source) ~[?:?]
……
……
Caused by: java.sql.SQLException: Data source is closed
at org.apache.commons.dbcp.BasicDataSource.createDataSource(BasicDataSource.java:1362) ~[commons-dbcp-1.4.jar:1.4]
at org.apache.commons.dbcp.BasicDataSource.getConnection(BasicDataSource.java:1044) ~[commons-dbcp-1.4.jar:1.4]
- 问题的查找,根据报错查到了以下几个链接:
https://issues.apache.org/jira/browse/DBCP-447
我们和提问题的人使用的dbcp版本是一致的,但最后的回答人确认不是问题。这个报错是因为1.3 1.4增加了校验的功能,如果发现数据源关闭了,则抛出这个异常。
https://stackoverflow.com/questions/33238061/receiving-data-source-is-closed-error-using-spring-boot-1-2-7-in-tomcat-8-java-8
说这个是spring4中的一个bug已经修复,但我们用的虽然是tomcat7 jdk7, 但spring用的是3的版本,所以还是不能确定是开源包的问题。
再看堆栈信息,spring调用了BasicDataSource.getConnection方法,getconnection方法又调用了createDataSource方法,遂打开dbcp源码。
/**
* Create (if necessary) and return a connection to the database.
*
* @throws SQLException if a database access error occurs
* @return a database connection
*/
public Connection getConnection() throws SQLException {
return createDataSource().getConnection();//调用createDataSource()
}
1362行的代码
/**
* <p>Create (if necessary) and return the internal data source we are
* using to manage our connections.</p>
*
* <p><strong>IMPLEMENTATION NOTE</strong> - It is tempting to use the
* "double checked locking" idiom in an attempt to avoid synchronizing
* on every single call to this method. However, this idiom fails to
* work correctly in the face of some optimizations that are legal for
* a JVM to perform.</p>
*
* @throws SQLException if the object pool cannot be created.
*/
protected synchronized DataSource createDataSource()
throws SQLException {
if (closed) {
throw new SQLException("Data source is closed");//1362行 1362行 1362行
}
可以看到之所以抛出 Data source is closed,是因为closed=true。这个closed看起来是数据源关闭的一个标识,具体再看看它的相关代码:
protected boolean closed;
/**
* <p>Closes and releases all idle connections that are currently stored in the connection pool
* associated with this data source.</p>
*
* <p>Connections that are checked out to clients when this method is invoked are not affected.
* When client applications subsequently invoke {@link Connection#close()} to return
* these connections to the pool, the underlying JDBC connections are closed.</p>
*
* <p>Attempts to acquire connections using {@link #getConnection()} after this method has been
* invoked result in SQLExceptions.<p>
*
* <p>This method is idempotent - i.e., closing an already closed BasicDataSource has no effect
* and does not generate exceptions.</p>
*
* @throws SQLException if an error occurs closing idle connections
*/
public synchronized void close() throws SQLException {
closed = true;
GenericObjectPool oldpool = connectionPool;
connectionPool = null;
dataSource = null;
try {
if (oldpool != null) {
oldpool.close();
}
} catch(SQLException e) {
throw e;
} catch(RuntimeException e) {
throw e;
} catch(Exception e) {
throw new SQLNestedException("Cannot close connection pool", e);
}
}
看注释,确实是dbcp关闭数据源的方法,并且在dbcp源码中close方法没有再调用的地方了。也就是说这个closed变成true一定是外部的因素造成的。spring? tomcat?
我们的数据源xml配置中确实有close的配置:
<bean id="parentDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
遂继续查找资料:
https://jybzjf.iteye.com/blog/1663222
得出推论:应当是容器重启或者关闭的时候会抛出此异常
- 打听当天的服务部署记录,确实有上线的情况,时间点与报错时刻吻合。至此排查完毕,没理解原理,虚惊一场。