Tomcat中重新部署项目后,旧项目运行时由Mybatis创建的MySQL连接未断开,解决方案

环境描述

Tomcat10服务器,Mybatis使用POOLED模式,使用MySQL数据库。

复现步骤

  • 在IDEA进行tomcat部署
  • 使用show processlist查看当前连接
  • 前端发送请求,后端通过mybatis创建数据库连接
  • 再调show processlist(多了一条sleep状态的连接A,POOLED模式下,mybatis不会释放连接)
  • 重新部署项目(非重启tomcat服务器)
  • tomcat日志报错
11-Jan-2024 15:51:45.914 警告 [RMI TCP Connection(9)-127.0.0.1] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesJdbc Web应用程序 [ROOT] 注册了JDBC驱动程序 [com.mysql.cj.jdbc.Driver],但在Web应用程序停止时无法注销它。 为防止内存泄漏,JDBC驱动程序已被强制取消注册。
11-Jan-2024 15:51:45.914 警告 [RMI TCP Connection(9)-127.0.0.1] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesJdbc Web应用程序 [ROOT] 注册了JDBC驱动程序 [org.apache.ibatis.datasource.unpooled.UnpooledDataSource.DriverProxy],但在Web应用程序停止时无法注销它。 为防止内存泄漏,JDBC驱动程序已被强制取消注册。
11-Jan-2024 15:51:45.915 警告 [RMI TCP Connection(9)-127.0.0.1] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads Web应用程序[ROOT]似乎启动了一个名为[mysql-cj-abandoned-connection-cleanup]的线程,但未能停止它。这很可能会造成内存泄漏。线程的堆栈跟踪:[
 java.base@17.0.8/java.lang.Object.wait(Native Method)
 java.base@17.0.8/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:155)
 com.mysql.cj.jdbc.AbandonedConnectionCleanupThread.run(AbandonedConnectionCleanupThread.java:93)
 java.base@17.0.8/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
 java.base@17.0.8/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
 java.base@17.0.8/java.lang.Thread.run(Thread.java:833)]
11-Jan-2024 15:51:50.562 信息 [mysql-cj-abandoned-connection-cleanup] org.apache.catalina.loader.WebappClassLoaderBase.checkStateForResourceLoading 非法访问:此Web应用程序实例已停止。无法加载[]。为了调试以及终止导致非法访问的线程,将抛出以下堆栈跟踪。
	java.lang.IllegalStateException: 非法访问:此Web应用程序实例已停止。无法加载[]。为了调试以及终止导致非法访问的线程,将抛出以下堆栈跟踪。
		at org.apache.catalina.loader.WebappClassLoaderBase.checkStateForResourceLoading(WebappClassLoaderBase.java:1373)
		at org.apache.catalina.loader.WebappClassLoaderBase.getResource(WebappClassLoaderBase.java:971)
		at com.mysql.cj.jdbc.AbandonedConnectionCleanupThread.checkThreadContextClassLoader(AbandonedConnectionCleanupThread.java:125)
		at com.mysql.cj.jdbc.AbandonedConnectionCleanupThread.run(AbandonedConnectionCleanupThread.java:92)
		at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
		at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
		at java.base/java.lang.Thread.run(Thread.java:833)
  • 前端再发请求
  • 再调show processlist(又多了一条连接B,A并未断开)

正常情况下,多次访问会优先使用连接池中已有的空闲链接,而不是新建。所以A连接已经处于无法正常使用的状态。

若重复以上步骤(重新部署,访问数据库,重新部署,访问数据库...),最终将消耗大量连接资源。(虽然只会在开发时才会有这种反复部署的需求吧)

解决方法

解决tomcat日志报错

tomcat日志报错实质与本问题无关,但可以利用其解决方法。

任意位置新建MyServletContextListener类:

package priv.liaohaolong.webapps.telephonebook.utils;

import com.mysql.cj.jdbc.AbandonedConnectionCleanupThread;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.annotation.WebListener;

import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;

@WebListener
public class MyServletContextListener implements ServletContextListener {
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // 如果Web应用拥有多个数据库的连接,可以一并关闭
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        Driver driver = null;
        while (drivers.hasMoreElements()) {
            try {
                driver = drivers.nextElement();
                DriverManager.deregisterDriver(driver);
            } catch (SQLException ignored) {
            }
        }
        AbandonedConnectionCleanupThread.uncheckedShutdown();
    }
}

tomcat日志报错解决方法来自:CSDN博客

解决连接未正确释放

原理:从sqlSessionFactory -> mybatis配置 -> evironment配置中获取dataSource实例,如果使用的是POOLED模式,则为PooledDataSource实例,其中提供了forceCloseAll来强制断开连接。

DataSource dataSource = sqlSessionFactory.getConfiguration().getEnvironment().getDataSource();
        if (dataSource instanceof PooledDataSource pooledDataSource)
            pooledDataSource.forceCloseAll();

在你的sqlSessionFactory管理类中,将上述三行包装成一个方法

最后在MyServletContextListener#contextDestroyed()的开头中调用即可。

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值