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

相关推荐
皮皮林5517 小时前
Java性能调优黑科技!1行代码实现毫秒级耗时追踪,效率飙升300%!
java
冰_河7 小时前
QPS从300到3100:我靠一行代码让接口性能暴涨10倍,系统性能原地起飞!!
java·后端·性能优化
桦说编程10 小时前
从 ForkJoinPool 的 Compensate 看并发框架的线程补偿思想
java·后端·源码阅读
躺平大鹅11 小时前
Java面向对象入门(类与对象,新手秒懂)
java
初次攀爬者12 小时前
RocketMQ在Spring Boot上的基础使用
java·spring boot·rocketmq
花花无缺12 小时前
搞懂@Autowired 与@Resuorce
java·spring boot·后端
Derek_Smart14 小时前
从一次 OOM 事故说起:打造生产级的 JVM 健康检查组件
java·jvm·spring boot
NE_STOP15 小时前
MyBatis-mybatis入门与增删改查
java
孟陬18 小时前
国外技术周刊 #1:Paul Graham 重新分享最受欢迎的文章《创作者的品味》、本周被划线最多 YouTube《如何在 19 分钟内学会 AI》、为何我不
java·前端·后端