Пытаясь найти утечку соединения БД в моем коде, используя Spring / JPA / Hikari
У меня проблема с веб-приложением Spring, которое периодически вызывает ошибку при получении соединения из моего пула соединений. В конце концов в журналах я вижу записи вроде:
- Вызвано: javax.persistence.PersistenceException: org.hibernate.exception.JDBCConnectionException: Невозможно получить соединение JDBC
- Вызвано: java.sql.SQLTransientConnectionException: HikariPool-1 - Соединение недоступно, время запроса истекло после 30000 мс.
Единственный способ восстановить, который я нашел, как только он достигнет этой точки, это перезапустить Tomcat.
Я думаю, что наиболее вероятное объяснение состоит в том, что у меня где-то есть код, который не очищает соединение должным образом - возвращая его в Hikari, оставляя что-то открытым, чтобы Spring не мог его очистить, и т. Д.
Чтобы устранить неполадки, я установил для своего параметра hikari leakDetectionThreshold значение 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);
}
Поэтому в основном я создаю объект Query из своего экземпляра EntityManager, устанавливаю некоторые параметры и запускаю его, чтобы получить некоторые результаты.
Есть ли что-то, что мне нужно делать с объектом Query, когда я закончу с ним? q.cleanup()? Я не вижу ничего подобного в чтении документов, но разве я не веду домашнюю работу на этом ресурсе?
Сам EntityManager создается из аннотации @Autowired. Насколько я понимаю, если я не "создал" его для создания экземпляра и вместо этого позволил инфраструктуре Spring автоматически подключить его, то Spring выполнит любую необходимую очистку. Это правильно? Или мне нужно сделать некоторую очистку после того, как я использую entityManager?
Детали версии:
- Tomcat 8 / Java 8
- Spring 5.0.0.RELEASE
- Spring Data Kay-RELEASE
- Hibernate 5.2.3.Final
- Хикари 2.4.5
Любые советы или предложения будут с благодарностью, спасибо!
1 ответ
Что за запрос? Это тяжело? Может быть, у вас здесь тупик? Управление подключением выглядит нормально. Вы не получаете соединение явно, поэтому нет необходимости освобождать его. Возможно, запрос выполняется долго, поэтому Hibernate не может завершить его и освободить соединение.
Также вы можете проверить количество открытых соединений на стороне БД. Сделайте некоторый анализ на той стороне также.