java.sql.SQLException: Connection has already been closed

背景:

通过一个接口触发后台数据库的批量更新操作,原本只是一个触发动作,不需要返回值,因此没有关心出现的http超时问题。后面发现批量更新任务中断了,查日志发现了Connection has already been closed报错。

具体的报错信息如下:

首先有一个警告:

24-Aug-2023 11:15:58.527 警告 [Tomcat JDBC Pool Cleaner[571592672:1692844532994]] org.apache.tomcat.jdbc.pool.ConnectionPool.abandon Connection has been abandoned PooledConnection[com.mysql.jdbc.JDBC4Connection@4a3901b4]:java.lang.Exception
    at org.apache.tomcat.jdbc.pool.ConnectionPool.getThreadDump(ConnectionPool.java:1102)
    at org.apache.tomcat.jdbc.pool.ConnectionPool.borrowConnection(ConnectionPool.java:807)
    at org.apache.tomcat.jdbc.pool.ConnectionPool.borrowConnection(ConnectionPool.java:651)
    at org.apache.tomcat.jdbc.pool.ConnectionPool.getConnection(ConnectionPool.java:198)
    at org.apache.tomcat.jdbc.pool.DataSourceProxy.getConnection(DataSourceProxy.java:132)
    at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:111)
    at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:77)
    at org.mybatis.spring.transaction.SpringManagedTransaction.openConnection(SpringManagedTransaction.java:82)
    at org.mybatis.spring.transaction.SpringManagedTransaction.getConnection(SpringManagedTransaction.java:68)
    at org.apache.ibatis.executor.BaseExecutor.getConnection(BaseExecutor.java:336)
    at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:84)
    at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:62)
    at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:324)
    at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:83)
    at sun.reflect.GeneratedMethodAccessor256.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.ibatis.plugin.Invocation.proceed(Invocation.java:49)
    at ...
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:148)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
    at sun.reflect.GeneratedMethodAccessor261.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433)
    at com.sun.proxy.$Proxy362.selectList(Unknown Source)
    at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:230)
    at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:137)
    at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:75)
    at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
    at com.sun.proxy.$Proxy463.page(Unknown Source)
    at ...

然后就是导致更新操作中断的一个报错:

2023-08-24 11:15:58.797 |[FA18413C-D23E-F8A6-2550-9384CE3F308B]| WARN | Error while extracting database name - falling back to empty error codes | org.springframework.jdbc.support.SQLErrorCodesFactory.getErrorCodes | SQLErrorCodesFactory.java:218 
org.springframework.jdbc.support.MetaDataAccessException: Error while extracting DatabaseMetaData; nested exception is java.sql.SQLException: Connection has already been closed.

分析:

根据报错的信息,可以看出是因为执行SQL时获取不到Connection连接,然后去看一下数据库配置中,有三个配置可以关注一下:

配置 默认值 说明
removeAbandoned false 是否强制关闭连接时长大于removeAbandonedTimeoutMillis的连接
removeAbandonedTimeoutMillis 300 * 1000 一个连接从被连接到被关闭之间的最大生命周期
logAbandoned false 强制关闭连接时是否记录日志

看了下我们项目的配置:

1.removeAbandoned是true,代表的意思是,关闭连接时长大于一定时长的连接

2.removeAbandonedTimeout是180,代表从被连接到被关闭之间的最大生命周期是3分钟

3.logAbandoned是true,代表强制关闭连接时记录日志

问题的原因可能就是出现在这里了,这里会循环遍历连接池中的连接,如果存活,就判断是否超过了配置的removeAbandonedTimeout,如果超过了时间,这个连接就要被干掉。因为spring中配置事务时配置的service开启一个事务,在service中拿到连接开启一个事务,而遍历中一直使用注入的dao去调用方法,其本质就是一直使用一个连接,不会遍历一次执行完重新获取连接,导致该连接超时被tomcat关闭回收。

可以尝试的解决方案:

1.适当增大 removeAbandonedTimeout时间,让单次获取的连接能够执行时间更长一点,让其支持更长一点的事务。

2.将所有dao层方法抽出来另放一个业务service层,注入dao层,方法里使用dao的调用方法。在原来的service层中注入业务service,原有dao调用的方法,全部替换成业务service调用的方法。这样每次业务service调用到(update、insert、delete)方法,就开启一个事务,执行完就回收。再执行就有获取一个连接,执行到事务方法后,又会主动关闭,就不会因连接超时被tomcat强行回收了!

你可能感兴趣的:(java,mysql)