-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 6 posts ] 
Author Message
 Post subject: Reuse Hibernate Session instances within a transaction
PostPosted: Wed Jul 07, 2004 3:51 am 
Newbie

Joined: Fri Jul 02, 2004 11:18 am
Posts: 5
Hi.

I'm beginning with Hibernate, and I'm trying to use it within JBoss CMT session beans. I find it a bit strange to open, flush and close a session each time I want to access the database inside a single JTA transaction. Indeed, I guess all the POJOs loaded in a first session are removed from the Hibernate cache for the second session. So I would like to associate a session instance with the current JTA transaction, and automatically close it at the end of the transaction. This should make development easier, and should increase performances.
I've read, in http://www.hibernate.org/110.html , that Spring offers this kind of functionality with its class SessionFactoryUtils. I tried it, but it seems to me that there is a bug in its conception. Indeed, when a method marked as RequiresNew gets a session from SessionFactoryUtils, it returns the same session as in the calling method. Correct me if I'm wrong, but this means that the database updates made in the RequiresNew transaction through this session will in fact be executed when the first transaction is committed, which is not correct.

So I've written my own code to handle this, and it seems to work well, but I would like to have some feedback about this code, in order to spot any mistake I could have done. If this code is correct, you may of course use it as you want.
Here's the code:

Code:

package com.objetdirect.hib.util;

import java.util.HashMap;
import java.util.Map;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.RollbackException;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;

import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.engine.SessionFactoryImplementor;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.objetdirect.hib.HibRuntimeException;

/**
* Class allowing for reuse of Hibernate Session instances within a transaction
* transaction.
*/
public class HibernateUtil {

  private static Log logger =
    LogFactory.getLog(HibernateUtil.class);

  private static ThreadLocal sessionSynchronizations = new ThreadLocal();

  public static SessionFactory lookupSessionFactory() throws NamingException {
    InitialContext initCtx = new InitialContext();
    return (SessionFactory) initCtx.lookup("java:/HibernateFactory");
  }

  /**
   * Gets a session from the given session factory. If a session has already
   * been asked for this session factory in the current JTA transaction, the
   * same session is returned. Else, a new session is created.
   * The returned session will automatically be flushed and closed at the end
   * of the current JTA transaction.
   * @param sessionFactory the factory from which to get the session
   * @return a session associated with the current JTA transaction.
   */ 
  public static Session getSession(SessionFactory sessionFactory) {
    try {
      Map map = getSessionSynchronizationMap();
      Key key = new Key(sessionFactory, getCurrentTransaction(sessionFactory));
      SessionSynchronization sync = (SessionSynchronization) map.get(key);
      if (sync == null) {
        Session session = sessionFactory.openSession();
        sync = new SessionSynchronization(key, session);
        map.put(key, sync);
        getCurrentTransaction(sessionFactory).registerSynchronization(sync);
      }
      return sync.getSession();
    }
    catch(SystemException e) {
      throw new HibRuntimeException(e);
    }
    catch(HibernateException e) {
      throw new HibRuntimeException(e);
    }
    catch(RollbackException e) {
      throw new HibRuntimeException(e);
    }
  }
 
  /**
   * Gets the thread local map storing the session synchronizations.
   * The returned map contains Key instances as keys and SessionSynchronization
   * instances as values
   */
  private static Map getSessionSynchronizationMap() {
    Map map = (Map) sessionSynchronizations.get();
    if (map == null) {
      map = new HashMap();
      sessionSynchronizations.set(map);
    }
    return map;
  }
 
  /**
   * Gets the current JTA transaction.
   * @param sessionFactory the session factory allowing to get the current
   * transaction
   * @return the current JTA transaction
   * @throws SystemException
   */
  private static Transaction getCurrentTransaction(SessionFactory sessionFactory) throws SystemException {
    SessionFactoryImplementor sessionFactoryImpl =
      (SessionFactoryImplementor) sessionFactory;
    TransactionManager txManager = sessionFactoryImpl.getTransactionManager();
    return txManager.getTransaction();
  }
 
  /**
   * Closes the given session and removes the session synchronization identified
   * by the given key from its map.
   */
  private static void closeSession(Key key, Session session) throws HibernateException {
    getSessionSynchronizationMap().remove(key);
    session.close();
  }

 
  /**
   * A class combining a session factory and a JTA transaction. It's used as
   * key in the map of session synchronizations.
   */
  private static class Key {
    private SessionFactory sessionFactory;
    private Transaction transaction;
   
    public Key(SessionFactory sessionFactory, Transaction transaction) {
      this.sessionFactory = sessionFactory;
      this.transaction = transaction;
    }
   
    public SessionFactory getSessionFactory() {
      return sessionFactory;
    }
   
    public Transaction getTransaction() {
      return transaction;
    }
   
    public boolean equals(Object o) {
      if (o == this) {
        return true;
      }
     
      if (o instanceof Key) {
        Key k = (Key) o;
        return ((k.transaction == this.transaction)
                && (k.sessionFactory == this.sessionFactory));
      }
      return false;
    }
   
    public int hashCode() {
      return (37 * transaction.hashCode()) + sessionFactory.hashCode();
    }
  }
 
  /**
   * Class allowing to close a session at the end of a JTA transaction.
   */
  private static class SessionSynchronization implements Synchronization {
   
    private Key key;
    private Session session;
   
    public SessionSynchronization(Key key, Session session) {
      this.key = key;
      this.session = session;
    }

    /**
     * @see javax.transaction.Synchronization#beforeCompletion()
     */
    public void beforeCompletion() {
      logger.debug("beforeCompletion called");
      try {
        session.flush();
        closeSession(key, session);
        this.session = null;
      }
      catch (HibernateException e) {
        logger.warn("impossible to flush or close the session", e);
        throw new HibRuntimeException(e);
      }
    }

    /**
     * @see javax.transaction.Synchronization#afterCompletion(int)
     */
    public void afterCompletion(int status) {
      logger.debug("afterCompletion called");
      if (this.session != null) {
        try {
          closeSession(key, session);
          this.session = null;
        }
        catch (HibernateException e) {
          logger.warn("impossible to close the session", e);
        }
      }
    }
   
    public Session getSession() {
      return session;
    }
  }
}


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 08, 2004 5:15 pm 
Senior
Senior

Joined: Wed Aug 27, 2003 6:04 am
Posts: 161
Location: Linz, Austria
I've integrated a similar strategy into Spring's SessionFactoryUtils, storing thread-bound Sessions per javax.transaction.Transaction to be able to handle suspended JTA transactions properly. This will be part of the upcoming Spring 1.1 RC1.

Juergen


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 09, 2004 2:48 am 
Newbie

Joined: Fri Jul 02, 2004 11:18 am
Posts: 5
Great. It'll be easier to convince collegues to adopt this strategy if it's implemented by Spring.

JB.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Sep 09, 2004 5:00 pm 
Newbie

Joined: Tue Dec 09, 2003 8:31 am
Posts: 17
Jnizet,

I was curious. I would like to use your HibernateUtil class, but I have a question about running JUnit tests. The HibernateUtil class has a method getCurrentTransaction() that gets the TransactionManager from SessionFactoryImpl. My question is, what TransactionManager do you use when running JUnit tests.

The junit tests I have run outside of the application server.

Hope that makes sense.

Thanks,

Craig


Top
 Profile  
 
 Post subject:
PostPosted: Fri Sep 10, 2004 12:08 pm 
Newbie

Joined: Tue Dec 09, 2003 8:31 am
Posts: 17
Just want to make a few comments on the HibernateUtil class.


I want to say up front that I really appreciate that the original poster made his code available by posting it to the forum. I started to integrate it into our application and ran a few tests to see if it would work. I really liked the idea of using this class and was very excited about getting it to work. Unfortunately, I have run into some problems that will most likely prevent me from using it.

First problem was that I couldn't use it when running JUnit tests since it relies on the TransactionManager to get a Transaction object. I was able to bypass that problem.

Second problem was that I have many SLSB methods I am only retrieving data, so those methods do not require a Transaction. Since there is no Transaction then the call to Transaction.registerSynchronization() will obviously not work. The callbacks to the Synchronization class is the thing that closes the session. So sessions would be left open without the callback.

I could mark all my methods on my EJBs to "Required" but not thrilled about that.

Maybe someone else reading this post will be able to use the class but I just wanted to make other people aware of the issues I had.

Again, thanks to the original poster. I am bummed that I cannot use it.

Craig


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jan 06, 2005 9:15 am 
Beginner
Beginner

Joined: Tue Oct 19, 2004 11:04 am
Posts: 22
craigduncan wrote:

First problem was that I couldn't use it when running JUnit tests since it relies on the TransactionManager to get a Transaction object. I was able to bypass that problem.



How did you bypass this problem?


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 6 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.