【问题标题】:Too Many Connections Error: Hibernate连接过多错误:休眠
【发布时间】:2015-03-23 06:51:19
【问题描述】:

我在使用纯 Servlet 和 JSP 开发的 Web 应用程序中使用 Hibernate。当我执行代码时,我“有时”会遇到很大的麻烦。发生的情况是我从 Hibernate 收到 Too many Connections 错误。

我经历了很多 Stackoverflow 问题以寻求答案,并找到了不同的解决方案。有人建议使用第三方池系统,有人建议线程安全,有人建议使用SessionFactory 等,因此我不确定哪一个适用于我的。

下面是我的数据库层的一部分。

package dao;

import java.util.List;
import model.main.Familyvisa;
import model.main.Familyvisa;
import model.main.Familyvisa;
import model.main.Pensionhistory;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;

/**
 *
 * @author user
 */
public class FamilyVisaImpl implements FamilyVisaInterface
{
    private Session currentSession; 
    private Transaction currentTransaction;

        public Session openCurrentSession() {
        currentSession = getSessionFactory().openSession();
        return currentSession;
    }

    public Session openCurrentSessionwithTransaction() {
        currentSession = getSessionFactory().openSession();
        currentTransaction = currentSession.beginTransaction();
        return currentSession;
    }

    public void closeCurrentSession() {
        currentSession.close();
    }

    public void closeCurrentSessionwithTransaction() {
        currentTransaction.commit();
        currentSession.close();
    }

    private static SessionFactory getSessionFactory() {

            Configuration configuration = new Configuration().configure();
            StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder()
                            .applySettings(configuration.getProperties());
            SessionFactory sessionFactory = configuration.buildSessionFactory(builder.build());
            return sessionFactory;
    }

       public Session getCurrentSession() {
        return currentSession;
    }

    public void setCurrentSession(Session currentSession) {
        this.currentSession = currentSession;
    }

    public Transaction getCurrentTransaction() {
        return currentTransaction;
    }

    public void setCurrentTransaction(Transaction currentTransaction) {
        this.currentTransaction = currentTransaction;
    }

         @Override
    public void save(Familyvisa entity) {
        getCurrentSession().save(entity);
    }

    @Override
    public void update(Familyvisa entity) {
        getCurrentSession().update(entity);
    }

    @Override
    public Familyvisa findById(int id) {
        Familyvisa book = (Familyvisa) getCurrentSession().get(Familyvisa.class, id);
        return book; 
    }

     @Override
    public void delete(Familyvisa entity) {
        getCurrentSession().delete(entity);
    }   

    @Override
        public List<Familyvisa> findAll() {
        List<Familyvisa> remDur = (List<Familyvisa>) getCurrentSession().createQuery("from Familyvisa").list();
        return remDur;
    }




     public Familyvisa findByForiegnKey_Family(int idFamily) 
    {
        String hql = "FROM  Familyvisa WHERE idFamily = :famId";
        //String hql = "FROM  Visa WHERE idFamily = :famId";
        Query q = getCurrentSession().createQuery(hql);
        q.setParameter("famId", idFamily);

        Familyvisa v = new Familyvisa();

        if(!q.list().isEmpty())
        {
            v = (Familyvisa)q.list().get(0);
        }

        return v;
    }


    @Override
    public void saveOrUpdate(Familyvisa p) 
    {
        getCurrentSession().saveOrUpdate(p);
    }

    @Override
    public List<Object[]> findReminderActiveVisaWithFamilyAndEmployee() 
    {
        String sql = "";

        SQLQuery createSQLQuery = getCurrentSession().createSQLQuery(sql);
        return createSQLQuery.list();
    }

    @Override
    public void batchUpdate(List<Familyvisa> list)
    {
        for(int i=0;i<list.size();i++)
        {
            getCurrentSession().update(list.get(i));
        }
    }
}

下面是我的服务层,和上面的代码有关。

package service;

import dao.FamilyVisaImpl;
import java.util.List;
import model.main.Familyvisa;


/**
 *
 * @author user
 */
public class FamilyVisaService
{
     private FamilyVisaImpl familyVisaImpl;

    public FamilyVisaService()
    {
        familyVisaImpl = new FamilyVisaImpl();
    }

    public Familyvisa findByForiegnKey_Family(int idFamily)
    {
        familyVisaImpl.openCurrentSession();
        Familyvisa findByForiegnKey_Family = familyVisaImpl.findByForiegnKey_Family(idFamily);
        familyVisaImpl.closeCurrentSession();
        return findByForiegnKey_Family;
    }

    public List<Object[]> findReminderActiveVisaWithFamilyAndEmployee()
    {
        familyVisaImpl.openCurrentSession();
         List<Object[]> visa = familyVisaImpl.findReminderActiveVisaWithFamilyAndEmployee();
        familyVisaImpl.closeCurrentSession();
        return visa;
    }

    public void batchUpdate(List<Familyvisa> list)
    {
        familyVisaImpl.openCurrentSessionwithTransaction();
        familyVisaImpl.batchUpdate(list);
        familyVisaImpl.closeCurrentSessionwithTransaction();
    }
}

下面是来自 Servlet 的代码,它解释了我是如何执行代码的。

private void updateDatabase(List<VisaWithFamilyAndEmployeeBean> reminderSentList)
    {
        FamilyVisaService service = new FamilyVisaService();
        List<Familyvisa> visa = new ArrayList<Familyvisa>();

        for(int i=0;i<reminderSentList.size();i++)
        {
            Familyvisa familyVisa = service.findByForiegnKey_Family(reminderSentList.get(i).getIdFamily());
            familyVisa.setNumberOfReminders(familyVisa.getNumberOfReminders()+1);
            familyVisa.setLastReminderSent(Common.getCurrentDateSQL());
            visa.add(familyVisa);
        }

        service.batchUpdate(visa);
    }

我在三层(servlet、DAO、Service)中有很多类,它们都遵循完全相同的结构,服务于不同的目的,但方法看起来几乎相同(如更新、插入等)。

请注意代码、关键字、访问说明符的使用等。在其他一些类中,在服务层中,我将其 IMPL 定义为 static 以及例如:private static EmployeeImpl employeeimpl;

你能找到这里发生的错误吗?因为它只发生在“有时”并且在任何代码中(不仅在这里,其他类也相同,唯一的区别是它们调用不同的表)所以我可以弄清楚。

更新

考虑到 cmets 和答案,我将代码更改为以下。请让我知道它是否在质量水平。

FamilyVisaService service = new FamilyVisaService();
Session session = service.openCurrentSession(); //This method will call openCurrentSession() in Impl class


try {

  for(int i=0;i<reminderSentList.size();i++)
    {
        /* findByForiegnKey_Family() has Session argument now! */
        Familyvisa familyVisa = service.findByForiegnKey_Family(session, reminderSentList.get(i).getIdFamily());
        familyVisa.setNumberOfReminders(familyVisa.getNumberOfReminders()+1);
        familyVisa.setLastReminderSent(Common.getCurrentDateSQL());
        visa.add(familyVisa);
    } 
  } catch (Exception ex) {
      System.out.println("ERROR:"+ex);
  } finally {
      session.close();
  }

【问题讨论】:

    标签: java database hibernate servlets connection-pooling


    【解决方案1】:

    你的sn-p代码:

    for(int i=0;i<reminderSentList.size();i++)
       {
          Familyvisa familyVisa = service.findByForiegnKey_Family(reminderSentList.get(i).getIdFamily());
          familyVisa.setNumberOfReminders(familyVisa.getNumberOfReminders()+1);
          familyVisa.setLastReminderSent(Common.getCurrentDateSQL());
          visa.add(familyVisa);
       }
    

    使用service.findByForeignKey_Family()函数在循环内多次打开和关闭会话。

    会话打开和关闭可能需要一些时间,但循环速度足够快。这就是为什么可以打开多个会话:它只需要时间来关闭。在你的代码中它是真实的。这就是出现“Too Many Connections”错误的原因。

    换句话说,将session 作为参数传递给service.findByForiegnKey_Family(),而不是打开和关闭这个内部函数。

    像这样:

    Session session = ...
    try {
    
      for(int i=0;i<reminderSentList.size();i++)
        {
            /* findByForiegnKey_Family() has Session argument now! */
            Familyvisa familyVisa = service.findByForiegnKey_Family(session, reminderSentList.get(i).getIdFamily());
            familyVisa.setNumberOfReminders(familyVisa.getNumberOfReminders()+1);
            familyVisa.setLastReminderSent(Common.getCurrentDateSQL());
            visa.add(familyVisa);
        } 
      } catch (Exception ex) {
          System.out.println("ERROR:"+ex);
      } finally {
          session.close();
      }
    

    上面的例子是线程安全的。因为你在单个函数中打开、操作和关闭会话。


    即使是读操作,Hibernate 也需要事务块。所以你必须像这样修复你的代码:

    Session session = ...
    try {
    session.beginTransaction();
    ...
    Your Loop
    ...
    session.getTransaction.commit();
    ...
    

    【讨论】:

    • 很有趣,但似乎我必须做很多改变?
    • @JustCause,实际上你只需要在.findByForiegnKey_Family() 函数和循环之外打开和关闭会话。 + 拒绝非事务性操作。就是这样
    • 啊,这样你的整个代码都在一个函数里面。好的,我以为session 是一个全局变量,因为你还没有声明一个函数。
    • 我添加了一个更新,另一个有一些更改的小代码,我已经根据答案进行了更改。请看一下,如果有任何问题或是否正常,请告诉我。
    • 您忘记了交易块。最好添加它以防止意外结果。其余的都可以,应该可以正常工作。
    【解决方案2】:

    你的代码在很多方面都是错误的:

    1. 正如您已经承认的那样,代码不是线程安全的:

      private Session currentSession; 
      private Transaction currentTransaction;
      
      public Session openCurrentSession() {
          currentSession = getSessionFactory().openSession();
          return currentSession;
      }
      
      public Session openCurrentSessionwithTransaction() {
          currentSession = getSessionFactory().openSession();
          currentTransaction = currentSession.beginTransaction();
          return currentSession;
      }
      
      public void closeCurrentSession() {
          currentSession.close();
      }
      
      public void closeCurrentSessionwithTransaction() {
          currentTransaction.commit();
          currentSession.close();
      }
      

      服务层单例不应该存储状态,因为它们是由并发请求访问的。如果您有一个当前正在运行的会话并且第二个请求也打开了一个新会话怎么办?第一个线程永远不会有机会关闭他的会话,但它会尝试关闭最后打开的会话(例如 currentSession)。

    2. Session 甚至不是线程安全的,因此您会遇到各种奇怪的并发修改或更改可见性错误。

    3. 您应该遵循 Hibernate 会话管理最佳实践并选择由 ThreadLocal 会话存储支持的 session-per-request 解决方案。

    4. 添加Spring Transaction Management是处理连接/会话/事务管理的一种简单有效的方法。

    【讨论】:

    • 1.由于class level 变量,它不是线程安全的吗?但它不是在Servlet 而是在另一个班级? 2. 没抓住重点。 3.看起来像“关闭Finally block内的会话?4.我想我们为时已晚。最后,SessionFactory的创建呢?看起来,它每次都在updateDatabase的循环中打开新实例
    • 1.多个请求可以调用openCurrentSession,因此设置session,每个会话都应该绑定到调用线程。在您的情况下,session 持有最后一个打开的会话,而且这甚至都不是线程安全的。 3.即使你在finally块上调用close,你确定你关闭了你一开始打开的session
    • 1. Multiple requests can call openCurrentSession and therefore set the session, and each session should be bound to the calling Thread - 我该怎么做?.. 请检查SessionFactory,我在方法中定义它..
    • FamilyVisaService 是单例,它有一个 FamilyVisaImpl 实例。多个请求调用FamilyVisaService 方法,因此同时调用openCurrentSession()。所以session 变量会被每个新的openCurrentSession() 调用覆盖。有意义吗?
    • FamilyVisaService 不是单例。我试图让它成为一个,但后来我停了下来。它不是单例。
    猜你喜欢
    • 2014-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-26
    • 2015-10-01
    • 2016-04-20
    相关资源
    最近更新 更多