【问题标题】:Connection unrecoverable after short network disconnect短暂断网后无法恢复连接
【发布时间】:2015-11-07 17:48:03
【问题描述】:

我想在我的 tomcat web 应用程序中使用 JPA。因此我创建了自己的Filter(在 web.xml 中提到)。

过滤器有三种方法:

  • init 方法创建一个 EntityManagerFactory 并将其存储在实例字段中(在应用程序部署时执行)
  • doFilter 方法使用存储的 EntityManagerFactory 创建一个 entityManager,启动一个事务并在处理请求后关闭事务(和 entityManager)
  • destroy 方法关闭存储的 EntityManagerFactory

这种机制运行良好且速度非常快 - 但一旦数据库连接丢失,应用程序将无法恢复

如何刷新 EntityManagerFactory,以便它创建一个新的数据库连接?还是我应该销毁并重新创建 EntityManagerFactory?我是否必须运行数据库查询来检查连接是否存在?

我当前的过滤器是这样实现的:

package entityManagerTest;

import java.io.IOException;

import javax.persistence.*;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;

@WebFilter(urlPatterns = { "/JpaServlet" })
public class JpaFilter implements Filter {

    private EntityManagerFactory entityManagerFactory;

    @Override
    public void init(FilterConfig config) throws ServletException {
        entityManagerFactory = Persistence.createEntityManagerFactory("test");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        EntityManager entityManager = entityManagerFactory .createEntityManager();
        try {
            EntityTransaction transaction = entityManager.getTransaction();
            transaction.begin();
            try {
                request.setAttribute("entityManager", entityManager);
                chain.doFilter(request, response);
            } finally {
                transaction.commit();
            }
        } finally {
            entityManager.close();
        }
    }

    @Override
    public void destroy() {
        entityManagerFactory.close();
    }
}

servlet 如下所示:

package entityManagerTest;

import java.io.*;
import javax.persistence.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;

@WebServlet(urlPatterns={"/JpaServlet"})
public class JpaServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        EntityManager entityManager = (EntityManager) req.getAttribute("entityManager");

        Query query = entityManager.createNativeQuery("SELECT 42");
        Object singleResult = query.getSingleResult();

        PrintWriter writer = resp.getWriter();
        writer.write("DB:"+singleResult);
        writer.close();
    }
}

我的persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="test">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <properties>
            <property name="javax.persistence.jdbc.driver" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
            <property name="javax.persistence.jdbc.url" value="jdbc:sqlserver:..." />
            <property name="javax.persistence.jdbc.user" value="x" />
            <property name="javax.persistence.jdbc.password" value="y" />
        </properties>
    </persistence-unit>
</persistence>

我尝试添加一个 validationQuery,但没有看到任何行为变化:

<property name="hibernate.connection.validationQuery" value="select 1"/>
<property name="connection.validationQuery" value="select 1"/>

当连接丢失时,我得到一个回滚异常(并不奇怪):

Aug 18, 2015 11:41:30 AM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
WARN: SQL Error: 0, SQLState: 08S01
Aug 18, 2015 11:41:30 AM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
ERROR: Software caused connection abort: recv failed
Aug 18, 2015 11:41:30 AM org.apache.catalina.core.StandardWrapperValve invoke
SCHWERWIEGEND: Servlet.service() for servlet [entityManagerTest.JpaServlet] in context with path [/EntityManagerTests] threw exception
org.hibernate.TransactionException: rollback failed
    at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.rollback(AbstractTransactionImpl.java:217)
    at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:73)
    at entityManagerTest.JpaFilter.doFilter(JpaFilter.java:29)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:668)
    at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:223)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1517)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1474)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)
Caused by: org.hibernate.TransactionException: unable to rollback against JDBC connection
    at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.doRollback(JdbcTransaction.java:167)
    at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.rollback(AbstractTransactionImpl.java:211)
    ... 21 more
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: Die Verbindung wurde geschlossen.
    at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDriverError(SQLServerException.java:190)
    at com.microsoft.sqlserver.jdbc.SQLServerConnection.checkClosed(SQLServerConnection.java:389)
    at com.microsoft.sqlserver.jdbc.SQLServerConnection.rollback(SQLServerConnection.java:1955)
    at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.doRollback(JdbcTransaction.java:163)
    ... 22 more

但是在网络连接再次恢复后,代码仍然失败并出现异常(即使连接现在可以恢复):

Aug 18, 2015 11:42:54 AM org.apache.catalina.core.StandardWrapperValve invoke
SCHWERWIEGEND: Servlet.service() for servlet [entityManagerTest.JpaServlet] in context with path [/EntityManagerTests] threw exception
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 entityManagerTest.JpaFilter.doFilter(JpaFilter.java:24)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:668)
    at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:223)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1517)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1474)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)
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:1471)
    at org.hibernate.jpa.internal.TransactionImpl.begin(TransactionImpl.java:61)
    ... 20 more
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: Die Verbindung wurde geschlossen.
    at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDriverError(SQLServerException.java:190)
    at com.microsoft.sqlserver.jdbc.SQLServerConnection.checkClosed(SQLServerConnection.java:389)
    at com.microsoft.sqlserver.jdbc.SQLServerConnection.getAutoCommit(SQLServerConnection.java:1910)
    at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.doBegin(JdbcTransaction.java:68)
    ... 23 more

【问题讨论】:

  • EMF 不处理连接......这是 EntityManager 的角色。我没有看到你在你写的内容中关闭了 EntityManager
  • 最简单的解决方案是:使用连接池.. 我不知道像 tomcat 这样的 Web 容器,但每个应用服务器都提供连接池。 AFAIR,您还可以将连接池 JAR 加载到您的 webapp 依赖项中,声明并使用它,一切都很好。
  • @NeilStockton 感谢您的提示 - 我忘了提及,我正在关闭 doFilter 方法中的 entityManager。
  • 你如何告诉提供者如何连接到数据库?如果它直接连接,您的提供商将有自己的选项来决定如何在一个死机时刷新/重新连接连接。否则,容器的数据源将处理它
  • 查找 JPA 提供者的连接池配置。所有连接池都允许调用 SQL 来检查连接是否处于活动状态并在必要时重新创建。

标签: java hibernate tomcat jpa entitymanager


【解决方案1】:

摘自the official hibernate documentation

然而,Hibernate 自己的连接池算法相当 初级的。它旨在帮助您入门,不是 用于生产系统,甚至用于性能 测试。 [...] 例如,您可能喜欢使用 c3p0。

您必须指定连接池数据源。连接池通常在使用连接之前检查连接的有效性。这是连接池的 C3P0(连接池数据源)和 Hibernate 配置的示例。我希望它对你有用。

<?xml version="1.0" encoding="UTF-8"?>

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
    version="2.0">
    <persistence-unit name="test" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.SQLServerDialect" />
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.format_sql" value="true" />
            <property name="hibernate.hbm2ddl.auto" value="update"/>

            <!-- I am not exactly sure that which one is correct -->
            <!-- javax.persistence.* or hibernate.connection.* properties -->
            <property name="hibernate.connection.driver_class" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
            <property name="hibernate.connection.url" value="jdbc:sqlserver:..."/>
            <property name="hibernate.connection.username" value="x"/>
            <property name="hibernate.connection.password" value="y"/>


            <!-- Connection Pooling settings -->
            <property name="hibernate.connection.provider_class" value="org.hibernate.service.jdbc.connections.internal.C3P0ConnectionProvider" />
            <property name="hibernate.c3p0.max_size" value="100" />
            <property name="hibernate.c3p0.min_size" value="0" />
            <property name="hibernate.c3p0.acquire_increment" value="1" />
            <property name="hibernate.c3p0.idle_test_period" value="300" />
            <property name="hibernate.c3p0.max_statements" value="0" />
            <property name="hibernate.c3p0.timeout" value="100" />
            <property name="hibernate.c3p0.preferredTestQuery">SELECT 1;</property>

        </properties>
    </persistence-unit>
</persistence>

您还需要将 hibernate-c3p0 添加到您的类路径中。

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-c3p0</artifactId>
        <version>${hibernate.version}</version>
    </dependency>

【讨论】:

  • 有效!我用的是 4.3.11.Final 和 c3p0 可以在短暂的网络断开后恢复连接。谢谢!
  • 所以你只需要更改“persistence.xml”来告诉hibernate使用任意连接池就可以了?您的代码没有进一步的更改?问题是休眠本机连接算法?你能确认一下吗?
  • @Victor 因为我的回答被接受了,我认为它解决了问题。关于确认,我认为参考官方休眠文档是我所能做的。我希望它也对你有用。
  • 是的,但是请告诉我一些事情...您是否必须更改代码上的某些内容?至少要有一个想法……
猜你喜欢
  • 2016-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-18
  • 1970-01-01
  • 2012-11-19
  • 2017-11-19
  • 2012-11-11
相关资源
最近更新 更多