试图在我的代码中找到数据库连接泄漏,使用 Spring / JPA / Hikari

Posted

技术标签:

【中文标题】试图在我的代码中找到数据库连接泄漏,使用 Spring / JPA / Hikari【英文标题】:trying to find db connection leak in my code, using Spring / JPA / Hikari 【发布时间】:2018-10-08 09:17:27 【问题描述】:

我遇到了一个 Spring Web 应用程序的问题,该应用程序在从我的连接池中获取连接时会定期遇到错误。最终在日志中我看到如下条目:

原因:javax.persistence.PersistenceException:org.hibernate.exception.JDBCConnectionException:无法获取 JDBC 连接 原因:java.sql.SQLTransientConnectionException: HikariPool-1 - 连接不可用,请求在 30000 毫秒后超时。

一旦达到这一点,我发现的唯一恢复方法是重新启动 Tomcat。

我认为最可能的解释是我在某处有一些代码没有正确清理其连接 - 将其返回给 Hikari,让某些东西处于打开状态,因此 Spring 无法清理它,等等。

为了解决问题,我将我的 hikari 配置泄漏检测阈值设置为 5000 毫秒并启用了日志记录。之后,我会看到类似的日志条目

2018-04-24 19:53:56 WARN  ProxyLeakTask:87 - Connection leak detection 
triggered for org.postgresql.jdbc.PgConnection@664ec666, stack trace 
follows
java.lang.Exception: Apparent connection leak detected
    at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.getConnection(DatasourceConnectionProviderImpl.java:122)
    at org.hibernate.internal.NonContextualJdbcConnectionAccess.obtainConnection(NonContextualJdbcConnectionAccess.java:35)
    at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.acquireConnectionIfNeeded(LogicalConnectionManagedImpl.java:99)
    at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.getPhysicalConnection(LogicalConnectionManagedImpl.java:129)
    at org.hibernate.engine.jdbc.internal.StatementPreparerImpl.connection(StatementPreparerImpl.java:47)
    at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$5.doPrepare(StatementPreparerImpl.java:146)
    at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$StatementPreparationTemplate.prepareStatement(StatementPreparerImpl.java:172)
    at org.hibernate.engine.jdbc.internal.StatementPreparerImpl.prepareQueryStatement(StatementPreparerImpl.java:148)
    at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:1940)
    at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1909)
    at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1887)
    at org.hibernate.loader.Loader.doQuery(Loader.java:932)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:349)
    at org.hibernate.loader.Loader.doList(Loader.java:2615)
    at org.hibernate.loader.Loader.doList(Loader.java:2598)
    at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2430)
    at org.hibernate.loader.Loader.list(Loader.java:2425)
    at org.hibernate.loader.custom.CustomLoader.list(CustomLoader.java:335)
    at org.hibernate.internal.SessionImpl.listCustomQuery(SessionImpl.java:2129)
    at org.hibernate.internal.AbstractSharedSessionContract.list(AbstractSharedSessionContract.java:981)
    at org.hibernate.query.internal.NativeQueryImpl.doList(NativeQueryImpl.java:147)
    at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1398)
    at org.hibernate.query.internal.AbstractProducedQuery.getSingleResult(AbstractProducedQuery.java:1444)
    at sun.reflect.GeneratedMethodAccessor191.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$DeferredQueryInvocationHandler.invoke(SharedEntityManagerCreator.java:379)
    at com.sun.proxy.$Proxy163.getSingleResult(Unknown Source)
    at com.mycompany.web.jpa.util.DBHelper.getPagedMappedDbResults(DBHelper.java:76)
    at com.mycompany.web.jpa.repository.TaskRepositoryImpl.findTaskDetailsByStepIdAndIdIn(TaskRepositoryImpl.java:245)
......

所以它正在检测可能的泄漏。我想可能是误报吗?但这也是我的应用程序中唯一一个在 Spring 应用程序中经常使用的标准服务/存储库模式之外进行数据库访问的类,所以它看起来可能是罪魁祸首,这是我目前最好的领导。

无论如何,我在跟踪中看到的最后一段非库代码(即我写的东西,所以很可能是导致泄漏的原因!)是我的 DBHelper::getPagedMappedDbResults 方法,相关位包括在这里:

    Query q = entityManager.createNativeQuery(countQueryText);
    setQueryParameters(q, parameters);
    long numActualResults = 0;
    try 
        numActualResults = ((Number)q.getSingleResult()).longValue(); // line 76
     catch (Exception e) 
        System.out.println("just in case: " + e);
    

所以基本上我从我的 EntityManager 实例创建一个 Query 对象,设置一些参数,然后运行它以获得一些结果。

当我完成 Query 对象后,我需要对它做些什么吗? q.cleanup()?我在阅读文档时没有看到类似的内容,但是我是否没有在此资源上做好内务管理?

entityManager 本身是从@Autowired 注解创建的。我的理解是,如果我没有“新建”它来实例化它,而是让 Spring 框架自动装配它,那么 Spring 会做任何必要的清理工作。那正确吗?还是在使用 entityManager 后需要进行一些清理?

版本详情:

Tomcat 8 / Java 8 Spring 5.0.0.RELEASE Spring Data Kay-RELEASE 休眠 5.2.3.Final 光2.4.5

任何意见或建议将不胜感激,谢谢!

【问题讨论】:

释放查询对象不需要做任何事情。它的生命周期应该由库管理。如果这是连接泄漏的原因,我会指出您的休眠版本存在错误。我怀疑它在 NativeQueryImpl.doList() 中。下面的所有内容看起来都非常像我见过的每个休眠堆栈跟踪。您可以尝试分离 getSingleResult() 返回的对象,但我很确定这会引发异常,因为它不是已定义的实体。 【参考方案1】:

查询是什么?重吗?也许你在这里陷入僵局?连接管理看起来不错。您没有显式获取连接,因此无需释放它。该查询可能运行时间很长,因此 Hibernate 无法完成它并释放连接。

此外,您还可以检查数据库端打开的连接数。在这方面也做一些分析。

【讨论】:

以上是关于试图在我的代码中找到数据库连接泄漏,使用 Spring / JPA / Hikari的主要内容,如果未能解决你的问题,请参考以下文章

试图识别tomcat中废弃连接的来源

HikariCP:为啥连接泄漏和释放导致新数据库连接的“连接尝试超时”?

内存泄漏角度为1.5

.showsPhysics的内存泄漏

如何使用 weblogic 检测 jdbc 中的连接泄漏?

iOS XML Parser 内存泄漏与 KissXML