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强行回收了!

相关推荐
浮游本尊9 小时前
Java学习第22天 - 云原生与容器化
java
渣哥10 小时前
原来 Java 里线程安全集合有这么多种
java
间彧11 小时前
Spring Boot集成Spring Security完整指南
java
间彧11 小时前
Spring Secutiy基本原理及工作流程
java
Java水解12 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
Java水解12 小时前
Mysql查看执行计划、explain关键字详解(超详细)
后端·mysql
洛小豆14 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学14 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole15 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端
华仔啊15 小时前
基于 RuoYi-Vue 轻松实现单用户登录功能,亲测有效
java·vue.js·后端