Too Many Connections Error: Hibernate

Too Many Connections Error: Hibernate

我在使用纯 Servlet 和 JSP 开发的 Web 应用程序中使用 Hibernate。当我执行代码时,我遇到了一个大麻烦 "sometimes"。我从 Hibernate 收到 Too many Connections 错误。

我浏览了很多 Whosebug 问题来寻找答案,并且找到了不同的解决方案。有人建议使用第三方池系统,有人建议线程安全,有人建议使用一个 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、服务)中有很多 类,它们都遵循完全相同的结构,服务于不同的目的,但方法看起来几乎相同(如更新、插入等)。

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

你能找到这里出了什么问题吗?因为它只出现 "sometimes" 并且在任何代码中(不仅在这里,而且其他 类 也一样,唯一的区别是它们调用不同的表)所以我可以弄清楚。

更新

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

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();
  }

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

  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是处理connection/session/transaction管理的一种简单有效的方法。

您的代码片段:

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();
...