Hibernate,C3P0,postgres,Tomcat连接在事务中处于空闲状态

3
我们有一个应用程序,使用Hibernate连接到PostgreSQL数据库。我们使用C3P0作为连接池。 persistence.xml: org.hibernate.ejb.HibernatePersistence ---classes--- 保存对象:
public Entity saveOrUpdate(Entity entity, User u) {  
EntityTransaction tx = EntityManagerHelper.getEntityManager().getTransaction();  
try {  
  if(!tx.isActive())  
      tx.begin();  
          Entity result = null;  
      if (getID(entity) == null) {  
      EntityManagerHelper.getEntityManager().persist(entity);  
  } else {  
      result = EntityManagerHelper.getEntityManager().merge(entity);  
  }  
  tx.commit();    
  return result;  
  } catch (RuntimeException re) {  
      re.printStackTrace();  
      tx.rollback();  
      throw re;  
  }  
}  

正在加载对象:

@SuppressWarnings("unchecked")
    public List<Entity> findByProperty(String propertyName, final Object value,
            final int... rowStartIdxAndCount) {

        try {
            final String queryString = "select model from " + clazz.getName()
                    + " model where model." + propertyName + "=     :propertyValue";
            Query query = EntityManagerHelper.getEntityManager().createQuery(
                    queryString);
            query.setParameter("propertyValue", value);
            if (rowStartIdxAndCount != null && rowStartIdxAndCount.length > 0)     {
                int rowStartIdx = Math.max(0, rowStartIdxAndCount[0]);
                if (rowStartIdx > 0) {
                    query.setFirstResult(rowStartIdx);
                }

                if (rowStartIdxAndCount.length > 1) {
                    int rowCount = Math.max(0, rowStartIdxAndCount[1]);
                    if (rowCount > 0) {
                        query.setMaxResults(rowCount);
                    }
                }
            }
            final List<Entity> result = query.getResultList();
            return result;
        } catch (RuntimeException re) {
            re.printStackTrace();
            throw re;
        }
    }  

创建EntityManagerFactory和获取EntityManager:

private static EntityManagerFactory emf;
private static final ThreadLocal<EntityManager> threadLocal = new ThreadLocal<EntityManager>();  

public static EntityManager getEntityManager() throws HibernateException {
    EntityManager session = (EntityManager) threadLocal.get();

    if (session == null || !session.isOpen()) {
        session = (emf != null) ? emf.createEntityManager()
                : null;
        threadLocal.set(session);
    }

    return session;
}

问题在于,有时数据库连接会停留在“空闲事务”状态,并且此连接永远不会返回。几天后,应用程序因连接数超过池最大大小而停止响应。
当启用 hibernate.connection.autocommit 时,这些连接不会变成“空闲事务”,但它们仍然被阻塞,导致的问题是相同的。
我们做错了什么(缺少配置等)吗?
我注意到当我只使用急切加载时,就没有问题。但由于性能原因,我必须使用延迟加载。EntityManagerFactory 应该明确关闭吗?我希望不是,因为我们需要长时间运行应用程序,我怀疑当某人正在处理持久对象时,我不能重置它。
在日志中,我看到以下内容,但我不知道它是否与我们的问题有关:
java.lang.Exception: DEBUG -- CLOSE BY CLIENT STACK TRACE
    at com.mchange.v2.c3p0.impl.NewPooledConnection.close(NewPooledConnection.java:491)
    at com.mchange.v2.c3p0.impl.NewPooledConnection.close(NewPooledConnection.java:191)
    at     com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool$1PooledConnectionResourcePoolManager.dest    royResource(C3P0PooledConnectionPool.java:470)
    at     com.mchange.v2.resourcepool.BasicResourcePool$1DestroyResourceTask.run(BasicResourcePool.ja    va:964)
    at     com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread.run(ThreadPoolAsynchronousRunn    er.java:547)  

感谢任何帮助! :)
3个回答

4
我觉得你的配置不正确:
  1. transaction-type="RESOURCE_LOCAL" 意味着你不在JTA环境中。
  2. 属性名为 "hibernate.connection.release_mode",值为 "after_statement",结合 auto-commit = false 是非常罕见的。

如果你的连接提供程序支持积极释放(并且能够为同一事务中的每个语句返回相同的连接),则可以使用 "after_statement"。

可能会被 Hibernate 忽略 AFTER_STATEMENT(因为 Hibernate 检测到该释放模式与您的设置不兼容),而改用 AFTER_TRANSACTION... 但为了确保您不会错误使用它,请放心使用。

 <property name="hibernate.connection.release_mode" value="auto" />

这将在非JTA环境中设置AFTER_TRANSACTION释放模式。

我不确定这是否会解决您的问题(因为有些机会您已经在运行after_transaction模式)。如果它不能解决问题,请发表评论并需要进行更深入的调查。

编辑

顺便说一下,重建sessionFactory似乎非常奇怪。 SessionFactory通常是单例,并且sessionFactory的主要目的是非常高效地构建新会话。一般来说,没有理由重新创建它(这很耗时且无用,因为sessionFactory仅依赖于静态数据)。

我唯一能想到重新创建sessionFactory的原因是:如果您的应用程序在运行时更改数据模型-即创建新表或列-(只有在同时修改映射文件或字节代码-添加新注释,字段和类-时,这些更改才会被新会话工厂看到)。我假设您没有这样做。

编辑2

如我在之前的编辑中所说:避免重建sessionFactory。使此方法私有,以确保它不会被调用多次。如果您的代码正在重新构建sessionFactory,则可能是问题的原因,因为新的SessionFactory可能会消耗一些连接-由于关联的C3PO设置)。

另一点:您说禁用延迟加载后就没有问题了。因此,问题也可能是由于为延迟加载创建的会话未正确关闭而引起的。尝试调试延迟加载操作,以查看会话来自何处以及是否已关闭。


编辑3(作为对您最后一条评论的回复)

您面临着一个非常常见的架构设计问题,并且可以说有两种方法来解决它。好的和(非常)糟糕的。

(非常)糟糕的方法:使用视图中的打开会话模式。

这个想法是在生成视图时重新打开entityManager并重新附加实体,以便您不会遇到懒惰初始化异常。在短期内,这种方法将给您错误的感觉,即您的应用程序运行良好。一旦它在生产中有许多并发用户和/或越来越多的记录在您的数据库中:您有巨大的风险出现真正的大型性能问题(无论是关于内存使用还是响应时间)。

(这些潜在问题的根本原因是,在小型DB上进行开发时,您不会注意到这个或那个视图正在获取具有10个对象的懒惰初始化集合...但是在生产中,您的小集合将变成具有10000个对象的巨大集合!!!)

这些问题将很难修复,因为: - 它们将存在于多个视图中 - 它们将很难进行单元/负载测试(因为它们在视图层中)。

我认为,此方法只能用于从不会有巨大负荷或大量数据的小型非关键应用程序。

好的方法:使用分层架构。

视图层不接触实体管理器。该层从控制器层获取要显示的数据,所有数据都在那里:这里不需要获取延迟集合。
控制器层有两个角色: 1. 实现业务逻辑 2. 管理实体管理器的生命周期(和事务边界),并提供一个可由DAO层使用的实体管理器。
此外,控制器层必须向视图层提供完整的对象图。完整的对象图意味着如果视图需要显示该集合中的数据,则视图层不会收到未初始化的延迟集合。
DAO层只需执行查询以获取数据(在此编写JPA/HQL/SQL查询)。该层除使用控制器层提供的实体管理器外,不对实体管理器进行任何特殊处理。
DAO层必须提供各种查询来获取带或不带延迟集合的实体,以满足控制器层的所有需求。
分层架构方法的主要优点是,您很快就可以在开发过程中看到视图的要求,并在需要时能够适应和优化查询。(即您需要一个一个地修复所有的延迟初始化异常,但它将为您提供视图需求的良好视野)

你好,感谢您的回复! 您说得对,我实际上并没有使用JPA。
我尝试将Hibernate的release_mode设置为“auto”,但是它没有任何效果。
- user2858263
为了测试目的,我完全删除了rebuildSessionFactory方法。我在某个地方读到过,如果我从工厂获取entityManager,我总是必须手动关闭它(entityManager)-但我不知道在我们的应用程序中应该在哪里这样做-我不确定如何捕捉用户停止使用实体的时刻(它们有惰性字段-这就是为什么我不能在DAO方法中直接关闭em)。如果我可以在DAO中的select之后关闭em,然后在检索惰性数据到我的分离实体时将此实体重新附加到新会话,那就太好了。但这可能是不可能的(?)。 - user2858263
另外一个问题是:我不显式关闭会话,也不知道如何调试一些自动关闭的情况(如果有的话..?)。 - user2858263
你在谈论“视图中开放会话(open session in view)”模式。请查看我的编辑。 - ben75

0

我还修改了我的“选择方法” - 我将其包含在事务中(就像我的保存方法一样),并且行为发生了变化:就像启用了 hibernate.autocommit 一样,连接不会以“空闲事务”状态结束,但它们仍然被阻塞,并且它们的数量增加。它们不会被 hibernate.c3p0.maxIdleTime 设置销毁(其他连接会)。当我在每次选择后也关闭 Entitymanager 后,稍后会出现 LazyInitializationException。这些连接是否可能保留给懒加载的对象?


0

将您的hibernate.connection.release_mode属性值更改为after_transaction。我认为这应该可以解决您的问题


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