Spring Boot 中的 Postgres 连接已关闭错误

35

我正在运行一个Spring Boot应用程序来创建REST API。经常会出现错误,提示数据库连接已关闭,此后我无法对应用程序进行任何调用。我正在使用Postgres DB。以下是完整的堆栈跟踪:

org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is javax.persistence.PersistenceException: org.hibernate.TransactionException: JDBC begin transaction failed: 
    at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:431)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:373)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:457)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:276)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
    at com.sun.proxy.$Proxy91.findByUriMoniker(Unknown Source)
    at com.mypkg.businessobjects.OrderInfoBO.getOrderInfo(OrderInfoBO.java:76)
    at com.mypkg.controller.OrderInfoController.getOrderInfo(OrderInfoController.java:78)
    at sun.reflect.GeneratedMethodAccessor104.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:777)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:706)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:943)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:877)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:857)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
    at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:85)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:130)
    at com.mypkg.config.CORSFilter.doFilter(CORSFilter.java:39)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:60)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:132)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:60)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:132)
    at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:85)
    at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:61)
    at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
    at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:131)
    at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:56)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:45)
    at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:63)
    at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:58)
    at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:70)
    at io.undertow.security.handlers.SecurityInitialHandler.handleRequest(SecurityInitialHandler.java:76)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:261)
    at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:247)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:76)
    at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:166)
    at io.undertow.server.Connectors.executeRootHandler(Connectors.java:197)
    at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:759)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: javax.persistence.PersistenceException: org.hibernate.TransactionException: JDBC begin transaction failed: 
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1763)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1677)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:1771)
    at org.hibernate.jpa.internal.TransactionImpl.begin(TransactionImpl.java:64)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.beginTransaction(HibernateJpaDialect.java:159)
    at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:380)
    ... 56 more
Caused by: org.hibernate.TransactionException: JDBC begin transaction failed: 
    at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.doBegin(JdbcTransaction.java:76)
    at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.begin(AbstractTransactionImpl.java:162)
    at org.hibernate.internal.SessionImpl.beginTransaction(SessionImpl.java:1435)
    at org.hibernate.jpa.internal.TransactionImpl.begin(TransactionImpl.java:61)
    ... 58 more
Caused by: org.postgresql.util.PSQLException: This connection has been closed.
    at org.postgresql.jdbc2.AbstractJdbc2Connection.checkClosed(AbstractJdbc2Connection.java:833)
    at org.postgresql.jdbc2.AbstractJdbc2Connection.getAutoCommit(AbstractJdbc2Connection.java:794)
    at sun.reflect.GeneratedMethodAccessor35.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.apache.tomcat.jdbc.pool.ProxyConnection.invoke(ProxyConnection.java:126)
    at org.apache.tomcat.jdbc.pool.JdbcInterceptor.invoke(JdbcInterceptor.java:108)
    at org.apache.tomcat.jdbc.pool.DisposableConnectionFacade.invoke(DisposableConnectionFacade.java:81)
    at com.sun.proxy.$Proxy56.getAutoCommit(Unknown Source)
    at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.doBegin(JdbcTransaction.java:68)
    ... 61 more

当我重新启动应用程序时,它会消失。我认为这个问题发生在我重新启动Postgres数据库时。为什么会发生这种情况?

6个回答

44

其他帖子已经回答了一半,我想要表述得更明确。同时,我也希望更符合Spring Boot的风格。如有需要,请随意更改时间间隔。

选项1:从连接池中清除不可用的连接。

使用以下属性:

spring.datasource.test-on-borrow=true
spring.datasource.validation-query=SELECT 1;
spring.datasource.validation-interval=30000

选项2: 保持连接池中的连接活动。

使用以下属性:

spring.datasource.test-while-idle=true
spring.datasource.validation-query=SELECT 1;
spring.datasource.time-between-eviction-runs-millis=60000

选项3: 主动丢弃空闲连接。

使用以下属性(注意:我无法在Spring Boot上找到可靠的文档。此外,超时时间以秒为单位而非毫秒):

spring.datasource.remove-abandoned=true
spring.datasource.remove-abandoned-timeout=60

开机愉快!


10
Spring Boot 1.4更加明确地指出这些设置是特定于连接池实现的,需要正确添加前缀(例如 spring.datasource.tomcatspring.datasource.dbcpspring.datasource.dbcp2spring.datasource.hikari),而不是只用 spring.datasource。Spring 的松散绑定将它们传递给底层连接池实现。请查看您正在使用的连接池的文档,了解正确的配置参数是什么。上述配置在Tomcat和DBCP中似乎可以,但在Hikari中有所不同。 - sworisbreathing

14
非常有价值的问题,通常很多人都会遇到这个问题。异常通常发生在池和数据库之间的网络连接断开时(大多数情况下是由于重启引起的)。根据你指定的堆栈跟踪信息,很明显你正在使用jdbc pool来获取连接。 JDBC pool具有各种连接池设置的微调选项,可以记录池内部发生的情况。
您可以参考 详细的Apache文档池配置 来指定废弃超时时间。

检查removeAbandoned,removeAbandonedTimeout,logAbandoned参数。

此外,您还可以使用其他属性来进一步加强验证

使用testXXX和validationQuery进行连接有效性测试。


4
这个解决方案对您有帮助吗?我有类似的问题,但是这并没有帮到我。 - Mark.ewd
@Mark.ewd:如果还有用的话,你可以尝试一下https://dev59.com/Eeo6XIcBkEYKwwoYGwQZ#33660586 - Jonik
在Spring Boot中,如果你自动配置数据源,如果使用正确的Driver类(例如org.postgresql.Driver),连接池也会被自动配置。你可以通过在application.properties文件中提供以下属性来调整连接池:spring.datasource.test-on-borrow=true, spring.datasource.remove-abandoned=true, spring.datasource.validation-query=SELECT 1; - Rafal Borowiec
我发现这个问题与之相关 - “无法在事务中更改事务隔离级别。”。我删除了上面评论中提到的这些设置,然后它开始正常工作了。有什么想法为什么会这样? - Tisha
@Tisha,我没有建议更改隔离级别设置。请查看数据库驱动程序手册,并检查是否需要配置其他设置以启用弃用连接。 - CuriousMind

3
这个异常基本上表示JDBC连接已关闭,但这并不意味着数据库服务器未运行(有另外一个异常)。当DB服务器重新启动或者DB服务器断开连接(例如因为超时)时,就会发生这种情况。那么这里的问题是,为什么应用程序不会在新的HTTP请求上重新连接到服务器。
通常,这是连接池配置错误导致的,连接池应该在每次应用程序“借用”连接时验证连接。您只需要执行以下操作即可解决此问题:
spring.datasource.validation-query=SELECT 1;
spring.datasource.test-on-borrow=true

其他配置参数(来自其他答案)是优化项,不是严格要求的异常处理。

但有时即使JDBC池已经正确配置,仍可能存在应用程序错误,即应用程序在HTTP请求结束后仍然持有DB连接而未将其返回给JDBC池。 因此,JDBC池甚至无法验证DB连接(所有它知道的是DB连接被“分配”)。 这里的一般解决方案是确保应用程序返回连接并在每个HTTP请求上“借用”新连接。

一个这样的错误示例:

@Component
public MyService {
    @Resource
    private EntityManagerFactory emf;
    
    private EntityManager em;
    
    public MyService() {
         em = emf.createEntityManager();//em never return back its JDBC connection to the pool (using em.close())
    }
}

上述bug的解决方案是使用注入/管理的EntityManager(首选)。
@Component
public MyService {
    @PersistenceContext
    private EntityManager em;
}

如果你真的需要自己管理,可以为每个HTTP请求创建一个EntityManager,并在try-finally块中关闭它。

@Component
public MyService {
    @Resource
    private EntityManagerFactory emf;
    
    private EntityManager em;
    
    public void myMethod() {
         EntityManager em = emf.createEntityManager();
         try {
         
         } finaly {
            em.close();//do not forget other cleanup operations like rolling back the transaction
         }
    }
}

2

我曾遇到同样的问题,使用这个设置,同时使用来自Tomcat (org.apache.tomcat.jdbc.pool) 的DataSource连接Heroku Postgres

org.springframework.transaction.CannotCreateTransactionException: 
    Could not open JPA EntityManager for transaction
org.hibernate.TransactionException: JDBC begin transaction failed: ] 
    with root cause
org.postgresql.util.PSQLException: This connection has been closed.

我解决问题的方法是在数据源初始化代码中添加以下内容(借鉴自一个Grails问题):
dataSource.setTestOnBorrow(true);
dataSource.setTestWhileIdle(true);
dataSource.setTestOnReturn(true);
dataSource.setValidationQuery("SELECT 1");

我不确定是否需要这三个设置才能获得稳定的连接,也许不需要,但启用所有选项可能并不会对性能造成太大影响。

JavaDocs中阐明了正在发生的情况,请参见setTestOnBorrow()。令人惊讶的是,默认情况下不进行此类测试。


1

我曾经遇到过完全相同的问题,但在我的情况下,上述答案并没有帮助。我发现当执行长查询时会出现相同的错误。在我的情况下,我调用了findAll(Iterable ids)并传递了一个超过100,000个id的巨大列表。将列表分区(例如使用Apache Commons或Google Guava中的ListUtils)并使用较少的id调用findAll()解决了问题。


-1
当你在仓库中编写查询时,请尽量保留@Repository注解。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接