背景:
通過一個接口觸發后臺數據庫的批量更新操作,原本只是一個觸發動作,不需要返回值,因此沒有關心出現的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.Exceptionat 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強行回收了!