Guice JPA - "Это соединение было закрыто". ошибка

После того, как БД сбрасывает свободное соединение или БД отключается и резервируется, я получаю следующую ошибку в моем веб-приложении:

javax.persistence.PersistenceException: org.hibernate.exception.JDBCConnectionException: could not inspect JDBC autocommit mode
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1365)
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1293)
    at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:265)    
... 60 more
Caused by: org.hibernate.exception.JDBCConnectionException: could not inspect JDBC autocommit mode
    at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:131)
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:49)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:125)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:110)
    at org.hibernate.engine.jdbc.internal.LogicalConnectionImpl.isAutoCommit(LogicalConnectionImpl.java:395)
    at org.hibernate.engine.transaction.internal.TransactionCoordinatorImpl.afterNonTransactionalQuery(TransactionCoordinatorImpl.java:195)
    at org.hibernate.internal.SessionImpl.afterOperation(SessionImpl.java:565)
    at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1220)
    at org.hibernate.internal.QueryImpl.list(QueryImpl.java:101)
    at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:256)
... 70 more
Caused by: org.postgresql.util.PSQLException: This connection has been closed.
    at org.postgresql.jdbc2.AbstractJdbc2Connection.checkClosed(AbstractJdbc2Connection.java:712)
    at org.postgresql.jdbc2.AbstractJdbc2Connection.getAutoCommit(AbstractJdbc2Connection.java:678)
    at sun.reflect.GeneratedMethodAccessor138.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.apache.tomcat.jdbc.pool.ProxyConnection.invoke(ProxyConnection.java:126)
    at org.apache.tomcat.jdbc.pool.JdbcInterceptor.invoke(JdbcInterceptor.java:99)
    at org.apache.tomcat.jdbc.pool.DisposableConnectionFacade.invoke(DisposableConnectionFacade.java:63)
    at $Proxy66.getAutoCommit(Unknown Source)
    at org.hibernate.engine.jdbc.internal.LogicalConnectionImpl.isAutoCommit(LogicalConnectionImpl.java:392)

Когда это начинается, я получаю

SQL Error: 0, SQLState: 08006 - An I/O error occured while sending to the backend.

но после этого это только:

SQL Error: 0, SQLState: 08003 - This connection has been closed.

Проблема в том, что я установил testOnBorrow, поэтому я ожидаю получить только открытые соединения.

Если это помогает: пул обычно содержит сочетание хороших и плохих соединений, и проблема, кажется, со временем проясняется, но у меня был сервер, работающий в течение>12 часов, и все еще возвращались плохие соединения. После перезагрузки все работает нормально (какое-то время).

Я отладил проблему еще немного, и кажется, что пул возвращает плохие соединения, например, если после того, как у меня все соединения прерваны на БД, я получаю:

SQL Error: 0, SQLState: 57P01

а затем обычные вещи - убитые соединения возвращаются из пула. Вопрос: это проблема приложения?

Я попытался очистить пул через JMX, но это, похоже, не имеет никакого эффекта. Еще одна странная вещь: несмотря на то, что приложение, по-видимому, ничего не делает (проверено с помощью дампа потоков), компонент JMX показывает 7 активных и 0 незанятых соединений. Когда я выполняю запрос, требующий доступа к БД, я немедленно получаю ответ (хотя нет доступных свободных соединений), но JMX показывает 7 активных и 0 свободных подключений после этого.

PS. Может быть, я упускаю что-то очевидное, и это проблема управления соединением с моей стороны? Я использую JPA EntityManager, настроенный через persistence.xml, поэтому, возможно, я делаю что-то не так, и соединения не закрываются (возвращаются) должным образом после использования?

3 ответа

Решение

На самом деле я был прав, когда подозревал ошибку приложения.

Все это хорошо описано в выпуске 730. Автоматически запускаемый UnitOfWork никогда не заканчивается

При использовании JpaPersistService, если вы пытаетесь получить доступ к EntityManager за пределами активного UnitOfWork, Guice автоматически запустит его для вас. Однако, поскольку Guice не знает (и не может) знать, когда завершить работу этого UnitOfWork, он никогда не узнает.

Результат? Нарушающий поток будет зависать с одним и тем же EntityManager на протяжении всего жизненного цикла приложения. Это плохое состояние для запуска приложения, и наше неизбежно истощает доступную память через некоторое время и приводит к сбою.

Настоящим убийцей здесь является то, что совсем не очевидно, когда вы совершили эту ошибку. Единственным реальным предупреждением является то, что вы получаете противоречивые данные из вашей базы данных между различными потоками (из-за кэша первого уровня EM) или что потребление памяти приложениями продолжает расти. В моем случае это было активное соединение в пуле, которое заставило меня заподозрить его, и затем, когда я включил детальное ведение журнала, я заметил, что приложение вообще не заимствовало соединение из пула, вместо этого оно повторно использовало соединение, уже удерживаемое незакрытый EntityManager.

На самом деле есть несколько дубликатов этой проблемы: http://code.google.com/p/google-guice/issues/list?can=1&q=UnitOfWork

Дикий выстрел: код здесь предполагает, что validationInterval проверяется на testOnBorrow также.

Поскольку вы устанавливаете это значение от 30 секунд по умолчанию до 5 минут, это означает, что в течение 5 минут после того, как DB прервет соединение, вы все равно сможете получить это устаревшее соединение. Если время ожидания вашей БД меньше 5 минут... не повезло.

Для проверки этой теории вы можете установить validationInterval до смешного низкого значения.

Если это помогает (читай: мы нашли правую ручку), вы должны установить ее как минимум на более короткое время, чем время ожидания БД. Поэтому, когда БД решает сбросить простое соединение, нижний validationInterval удостоверится, что соединение проверено перед следующим заимствованием. Закрытые соединения из-за перезапуска сервера БД (т.е. без тайм-аута) не будут затронуты этим решением, но по крайней мере время возврата в нормальное состояние также будет ниже.

Примечание: я только что спросил у Google код. Я понятия не имею, является ли это действительным кодом или древним кодом.

Если вы изменили свой META-INF/context.xml включить validationQueryTomcat, вероятно, кеширует старое определение в conf/engine/host/webapp.xml, Завершите работу Tomcat, удалите этот файл и перезапустите Tomcat. Не мешало бы убрать work каталог, пока Tomcat не работает.

Другие вопросы по тегам