有关java.sql.SQLException: Data source is closed

  1. 问题的现象,线上的系统莫明的报错

### 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]
  1. 问题的查找,根据报错查到了以下几个链接:
    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行的代码


    /**
     * 

Create (if necessary) and return the internal data source we are * using to manage our connections.

* *

IMPLEMENTATION NOTE - 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.

* * @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;

    /**
     * 

Closes and releases all idle connections that are currently stored in the connection pool * associated with this data source.

* *

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.

* *

Attempts to acquire connections using {@link #getConnection()} after this method has been * invoked result in SQLExceptions.

* *

This method is idempotent - i.e., closing an already closed BasicDataSource has no effect * and does not generate exceptions.

* * @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的配置:

 

遂继续查找资料:
https://jybzjf.iteye.com/blog/1663222
得出推论:应当是容器重启或者关闭的时候会抛出此异常

  1. 打听当天的服务部署记录,确实有上线的情况,时间点与报错时刻吻合。至此排查完毕,没理解原理,虚惊一场。

你可能感兴趣的:(技术分享)