Hibernate 3: DB-Verbindungen bleiben nach Ende der Applikation offen

YeahBuddy

Grünschnabel
Hallo Forum,

mal eine Frage zu Hibernate/MySQL:

Ist es normal, dass die Pooling-Connections die sich Hibernate bei mir mit c3p0 für MySQL holt, nach dem Ende der Anwendung nicht geschlossen werden? (Sichtbar im Mysql-Admin unter "Aktive Verbindungen"). Bei jedem neuen Start der App werden nämlich wieder neue Connections gepoolt, bei meiner Einstellung von mindestens 15 Connections hab ich nach 3 Starts 45 Verbindungen offen (und so weiter, biss der DB-Server sie anhand des Timeouts schliesst, was ewig dauern kann. Und die Einstellung des DB-Timeouts liegt nicht in meiner Macht).

Wenn das nicht normal ist, wie kann ich ich das Schliessen der Verbindungen beim Beenden der Anwendung forcieren?

Meine Hibernate-Config sieht folgendermaßen aus:

Code:
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
   "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
   "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
   <session-factory>
      <property name="connection.url">
         jdbc:mysql://server:3306/db
      </property>
      <property name="connection.username">user</property>
      <property name="connection.password">password</property>
      <property name="connection.driver_class">
         com.mysql.jdbc.Driver
      </property>
      <property name="dialect">
         org.hibernate.dialect.MySQL5InnoDBDialect
      </property>
      <property name="cache.provider_class">
         org.hibernate.cache.EhCacheProvider
      </property>
      <property name="current_session_context_class">
      	thread
      </property>
      <property name="hibernate.transaction.factory_class">
         org.hibernate.transaction.JDBCTransactionFactory
      </property>
      
      <property name="hibernate.connection.useUnicode">
      	true
      </property>
      <property name="hibernate.connection.charSet">
      	UTF-8
      </property>
      <property name="hibernate.connection.characterEncoding">
      	UTF-8
      </property>
      
      <property name="hibernate.hbm2ddl.auto">
      	update
      </property>
	  
	  <property name="hibernate.c3p0.acquire_increment">
	  	1
	  </property>
	  <property name="hibernate.c3p0.idle_test_period">
	  	100
	  </property>
	  <property name="hibernate.c3p0.initialPoolSize">
	  	10
	  </property>
      <property name="hibernate.c3p0.min_size">
      	15
      </property>
      <property name="hibernate.c3p0.max_size">
      	50
      </property>
      <property name="hibernate.c3p0.timeout">
      	1800
      </property>
            
      <mapping class="........." />
	 
   </session-factory>
</hibernate-configuration>

Zum statischen Zugriff auf Session und Transaktionen verwende ich das ThreadLocal-Pattern.
Code:
package ie.gov.agriculture.dpcs.hibernate; 

import java.io.FileInputStream; 
import java.util.Properties; 

import org.apache.commons.logging.Log; 
import org.apache.commons.logging.LogFactory; 
import org.hibernate.HibernateException; 
import org.hibernate.Session; 
import org.hibernate.SessionFactory; 
import org.hibernate.Transaction; 
import org.hibernate.cfg.Configuration; 
import org.hibernate.exception.ConstraintViolationException; 

public class HibernateUtil { 

   private static final SessionFactory sessionFactory; 
    
    private static final ThreadLocal threadSession = new ThreadLocal(); 
    
    private static final ThreadLocal threadTransaction = new ThreadLocal(); 
    
    private static Log log = LogFactory.getLog(HibernateUtil.class); 
    

    
    static { 
        try { 
            // Create the SessionFactory from hibernate.cfg.xml 
           sessionFactory = new Configuration().configure().buildSessionFactory(); 
           log.debug("Created Hibernate Session Factory"); 
           
        } catch (Throwable ex) { 
            // Make sure you log the exception, as it might be swallowed 
            log.debug("Initial SessionFactory creation failed." + ex); 
            throw new ExceptionInInitializerError(ex); 
        } 
    } 


    /** 
     * 
     * @return 
     */ 
    public static SessionFactory getSessionFactory() { 
        return sessionFactory; 
    } 

    /** 
     * Create a new Session if no session exists 
     * and add it to the current thread. 
     * 
     * @return the current session 
     */ 
    public static Session getSession() { 

       Session s = (Session)threadSession.get(); 
       // Open a new Session, it this thread has none yet 
        
       try { 
          if(s== null) { 
             log.debug("SessionFactory is closed = " + sessionFactory.isClosed()); 
             s = sessionFactory.openSession(); 
             log.debug("Session is connected = " + s.isConnected()+ " And is open = " + s.isOpen()); 
             log.debug("HibernateUtil::Opening new Hibernate Session"); 
             threadSession.set(s); 
             log.debug("HibernateUtil::Added Session to current Thread"); 
              

          } 
           
       } catch (HibernateException e) { 
                     
          log.debug("HibernateException Getting Session::" + e.getMessage()); 
          e.printStackTrace(); 
       } 
        
       return s; 
    } 
    
    /** 
     * Remove the session from the current thread 
     * and if it is open, close it. 
     * 
     */ 
    public static void closeSession() { 
        
       try { 
          Session s = (Session)threadSession.get(); 
          threadSession.set(null); 
         log.debug("HibernateUtil::Closing Hibernate Session"); 
          if( s != null && s.isOpen()) { 
             s.close(); 
          } 
           
       } catch (HibernateException e) { 
           
          log.debug("HibernateException Closing Session::" + e.getMessage()); 
          e.printStackTrace(); 
       } 
    } 
    
    /** 
     * If no transaction exists, create a new one and add it to 
     * this thread 
     * 
     */ 
    public static void beginTransaction() { 


       Transaction tx = (Transaction)threadTransaction.get(); 
       try{ 
          if(tx == null) { 
             log.debug("HibernateUtil::Starting new Hibernate Transaction"); 
             tx = getSession().beginTransaction(); 
             threadTransaction.set(tx); 
             log.debug("HibernateUtil::Added Hibernate Transaction to local thread:: tx is Active= " + tx.isActive()); 
          } 
           
       } catch (HibernateException e) { 
           
          log.debug("HibernateException Beginning Transaction::" + e.getMessage()); 
          e.printStackTrace(); 

       } 
    } 
    
    /** 
     * Commit the Transaction if it has not been comitted or 
     * rolled back already. 
     * 
     */ 
    public static void commitTransaction() { 
        
       Transaction tx = (Transaction)threadTransaction.get(); 
    
       try { 
         threadTransaction.set(null); 
          if(tx != null && !tx.wasCommitted() 
                     && !tx.wasRolledBack()) { 
             log.debug("HibernateUtil::Commiting Hibernate Transaction"); 
             tx.commit(); 
          } 
           
       } catch (ConstraintViolationException e) { 
           
          log.debug("HibernateException Rolling Back Transaction::" + e.getMessage()); 
          rollbackTransaction(); 
          throw e; 

       } catch (HibernateException e) { 
           
          log.debug("HibernateException Rolling Back Transaction::" + e.getMessage()); 
          rollbackTransaction(); 
          e.printStackTrace(); 
           
       } 
    } 
    
    public static void attemptCommitTransaction() { 
        
       Transaction tx = (Transaction)threadTransaction.get(); 
    
      threadTransaction.set(null); 
         if(tx != null && !tx.wasCommitted() 
                    && !tx.wasRolledBack()) { 
           log.debug("HibernateUtil::Attempting to Commit Hibernate Transaction"); 
            tx.commit(); 
         } 
           
    } 
    
    /** 
     * Roll back transaction if it has not been comitted 
     * or rolled back already 
     * 
     */ 
    public static void rollbackTransaction() { 
        
       Transaction tx = (Transaction)threadTransaction.get(); 
        
      log.debug("HibernateUtil::About to Rollback Hibernate Transaction:: tx is Active= " + tx.isActive());           
       try { 
         threadTransaction.set(null); 
         if(tx != null && !tx.wasCommitted() 
                    && !tx.wasRolledBack()) { 
            log.debug("HibernateUtil::Rollingback Hibernate Transaction");           
            tx.rollback(); 
         } 
       
       } catch (HibernateException e) { 
           
          log.debug("HibernateException Rolling Back Transaction::" + e.getMessage()); 
          e.printStackTrace(); 
       
      } finally { 
          
         closeSession(); 
      } 
        
    } 
}
 
[offtopic]*zwinker* ich sollte langsam mal anfangen wie viele (verschiedene / schöne / hässliche) HibernateUtil Klassen ich in meinem Leben schon gesehen hab... an dem Tag, an dem ich die letzte sehe, mache ich nen rotes Kreuz in den Kalender[/offtopic]

Einfache Gegenfrage: Wie sieht der Clientcode aus? Hast du die HibernateUtil selber geschrieben? Auf den ersten Blick sieht es nämlich so aus als würdest du im Gegensatz zum Rollback beim Commit die Session nicht schließen (was durchaus sinnvol sein kann). Rufst du closeSession beim Beenden des Programms auf?

Gruß
Ollie
 
Hi Oliver, danke für deine Antwort.
Es gibt wirkliche viele Arten, HibernateSessions statisch verfügbar zu machen, gibt sogar richtige Pattern-Definitionen dafür ;)

Die Klasse habe ich natürlich nicht selbst geschrieben, warum auch, man muss ja nicht alles doppelt machen ;)

CloseSession rufe ich explizit im Programmcode auf, wobei ich ausschliesslich attemptCommit benutze, statt der commit-Funktion im HibernateUtil, welche das komplette Errorhandlich samt Rolblack und Session schliessen mit eingeschlossen hat. (ich habe die Exception-Auswertung lieber an den Stellen, an denen auch der Commit passiert, und nicht zentral).


Die Session schliesse ich bei Programmende nicht explizit. Wäre das überhaupt machbar?


/edit: Das Problem ist, dass das Schliessen der Session per Close nicht wirklich auch die Datenbankverbindungen beendet, diese werden durchs Pooling geöffnet und verwaltet. Es betrifft also wohl eher die Einstellungen vom Pooling, die Datenbankverbindungen nach Ende des Programms wieder zu schliessen..(?)
 
Zuletzt bearbeitet:
Hi Oliver, danke für deine Antwort.
Es gibt wirkliche viele Arten, HibernateSessions statisch verfügbar zu machen, gibt sogar richtige Pattern-Definitionen dafür ;)

Die Klasse habe ich natürlich nicht selbst geschrieben, warum auch, man muss ja nicht alles doppelt machen ;)

*grins* Naja, man könnte es zumindest richtig machen... Offensichtlich ist das Aufräumen der Resourcen nicht sauber implementiert.

Ich finde es erstaunlich wie viele Entwickler es hinnehmen, dass ein API so unheimlich unbenutzbar ist, wie das von Hibernate. Aber das ist wahrscheinlich der Grund warum ich Hibernate ohne Spring nicht mehr anfassen mag. Das sorgt dann auch dafür, dass Resourcen vernünftig aufgeräumt werden.

CloseSession rufe ich explizit im Programmcode auf, wobei ich ausschliesslich attemptCommit benutze, statt der commit-Funktion im HibernateUtil, welche das komplette Errorhandlich samt Rolblack und Session schliessen mit eingeschlossen hat. (ich habe die Exception-Auswertung lieber an den Stellen, an denen auch der Commit passiert, und nicht zentral).

Die Session schliesse ich bei Programmende nicht explizit. Wäre das überhaupt machbar?

/edit: Das Problem ist, dass das Schliessen der Session per Close nicht wirklich auch die Datenbankverbindungen beendet, diese werden durchs Pooling geöffnet und verwaltet. Es betrifft also wohl eher die Einstellungen vom Pooling, die Datenbankverbindungen nach Ende des Programms wieder zu schliessen..(?)

Also die API sagt zu Session.close() folgendes:
End the session by releasing the JDBC connection and cleaning up. It is not strictly necessary to close the session but you must at least disconnect() it.
Weiterhin solltest du auch sicherstellen, dass SessionFactory.close() auf jeden Fall zum Schluss gerufen wird... das Räumt eigentlich wirklich alles weg. Wenn du schon die HibernateUtils nutzt, macht da drin ne statische Methode Sinn, die genau das tut.

Gruß
Ollie
 
Die Frage ist jetzt, wie ich beim Beenden der Applikation durch STRG-C noch das Aufrufen von Aufräum-Methoden anstoßen kann. Die Anwendung ist ein Server der nur Log-Ausgaben tätigt.
Das Schliessen der SessionFactory würde tatsächlich Erfolg bringen.
 
Dafür gibts shutdown hooks:

Java:
Runtime.getRuntime().addShutdownHook(
        new Thread(
          new Runnable() {
            public void run() {
              HibernateUtil.closeSessionFactory();
            }
          }));

Gruß
Ollie
 

Neue Beiträge

Zurück