-->
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.  [ 5 posts ] 
Author Message
 Post subject: Encapsulating Hibernate classes/methods
PostPosted: Fri Jan 09, 2004 4:12 pm 
Newbie

Joined: Tue Jan 06, 2004 3:49 pm
Posts: 11
Location: Montreal, Canada
We just got Hibernate properly configured with help from the forum :).

Our set-up: web app Tomcat-struts, Hibernate provided JDBC connection through C3P0

After performing a few querries, we are in the process of encapsulating some classes/methods of the Hibernate API. Though we are not sure this is in line with best practices....

We are currently using the HibernateUtil helper class presented within the 'Tomcat quickstart':

Code:
package model.util;

import java.util.Iterator;

import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Query;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.Transaction;

import net.sf.hibernate.cfg.Configuration;

public class HibernateUtil {

    private static final SessionFactory sessionFactory;

    static {
        try {
            sessionFactory = new Configuration().configure().buildSessionFactory();
        } catch (HibernateException ex) {
            throw new RuntimeException("Exception building SessionFactory: " + ex.getMessage(), ex);
        }
    }

    public static final ThreadLocal session = new ThreadLocal();

    public static Session currentSession() throws HibernateException {
        Session s = (Session) session.get();
        // Open a new Session, if this Thread has none yet
        if (s == null) {
            s = sessionFactory.openSession();
            session.set(s);
        }
        return s;
    }

    public static void closeSession() throws HibernateException {
        Session s = (Session) session.get();
        session.set(null);
        if (s != null)
            s.close();
    }
}


As a result, when we have to perform a query in one of our action classes, we have to do the following:

Code:
  import somePkg.HibernateUtil;
  import net.sf.hibernate.Query;
  import net.sf.hibernate.Session;
  import net.sf.hibernate.Transaction;
 
  (code removed for clarity) 

  Session hbSession = HibernateUtil.currentSession();
  Transaction tx= hbSession.beginTransaction();
     
  Query query = hbSession.createQuery("from dto.Customer as cust where cust.id = 29");
  query.setShort("selectedCust", anId);
  for (Iterator it = query.iterate(); it.hasNext();)
  {
        Customer aCust = (Customer) it.next();
        logger.debug("selected customer: id = " + aCust.getCustomerId() + " desc = " + aCust.getCustomerDesc());
  }
     
  tx.commit();
  HibernateUtil.closeSession();


Our objective would be to add helper methods in HibernateUtil so we would not have to import hibernate packages and not have to care about tx.commit() and closeSession in our action classes.

Would this be a good practice? Is there any example out there using this approach?

Here is an example of a helper method we would like to add to the HibernateUtil class; naturally this method is useless since it returns an Iterator on a result set which I think gets "garbage collected" once closeSession() is called. However it reflects what we would like to achieve.

Code:
    public static Iterator doQuery(String aQuery) throws HibernateException
    {
      Session hbSession = currentSession();
      Transaction tx= hbSession.beginTransaction();
     
      Iterator it =  hbSession.createQuery(aQuery).scroll();
     
      tx.commit();
      closeSession();
     
      return it;
    }   



Any comment are more than welcomed, even if it is to bash the approach we are considering :)

Thanks!


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jan 09, 2004 4:32 pm 
Pro
Pro

Joined: Tue Aug 26, 2003 1:24 pm
Posts: 213
Location: Richardson, TX
Quote:
Would this be a good practice?

It better be. My entire project is structured this way. :)

Quote:
Is there any example out there using this approach?

I can give you some broad examples.

My project has three tiers: persistence, business, application.

Persistence: Hibernate setup, generation of persistent objects. Uses Hibernate to connect to DB and provide generic data services to higher levels. Provides a generic "DataAccessWrapper" to higher layers that provides Hibernate agnostic methods for performing basic data operations. (Starting/stopping persistence, begin/commit/rollback transaction, etc) Provides Hibernate specific objects for use in Business tier.

Business: Directly access persistence layer, applies all business rules. Directly uses Hibernate to perform queries, object modification, creation and deletion.

Application: Accesses only generic (i.e. non-Hibernate specific) methods in persistence tier to manage starting/stopping persistence and begin/commit/rollback transaction.

At the moment I'm not using a thread local session, I'm passing my DataAccessWrapper to each business method as an argument. I'm not as annoyed by having that extra argument in each method call as some people seem to be. :)

I can post code if you like...


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jan 09, 2004 4:43 pm 
Newbie

Joined: Tue Jan 06, 2004 3:49 pm
Posts: 11
Location: Montreal, Canada
Thanks for your input greg_barton.

greg_barton
Quote:
I can post code if you like...


I would certainly appreciate to get a glimpse of your DataAccessWrapper ;)


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jan 09, 2004 7:41 pm 
Pro
Pro

Joined: Tue Aug 26, 2003 1:24 pm
Posts: 213
Location: Richardson, TX
k...here goes...

Code:
package com.foo.bar.data.access;

import com.foo.bar.data.exception.DataException;
import net.sf.hibernate.Hibernate;
import net.sf.hibernate.HibernateException;

public class DataAccess {

    public static final DataAccessWrapper getDataAccessWrapper() {

        return new DataAccessWrapperImpl();
    }

    public static final void initialize(Object o)
                                 throws DataException {

        try {
            Hibernate.initialize(o);
        } catch(HibernateException he) {
            throw new DataException(he);
        }
    }
}

Code:
package com.foo.bar.data.access;

import com.foo.bar.data.exception.DataException;
import net.sf.hibernate.Session;

public interface DataAccessWrapper {

    public Session getDataAccessObject() throws DataException;

    public void beginTransaction() throws DataException;

    public void cleanupDataAccessObject() throws DataException;

    public void commitTransaction() throws DataException;

    public void replicate(Object replicant) throws DataException;

    public void rollbackTransaction();

    public void save(Object object) throws DataException;
}

Code:
package com.foo.bar.data.access;

import com.foo.bar.common.Logger;
import com.foo.bar.data.exception.DataException;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.cfg.Configuration;
import java.io.Serializable;
import java.sql.SQLException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public abstract class AbstractDataAccessWrapper
    implements DataAccessWrapper,
               Serializable {

    private static SessionFactory FACTORY = null;

    private static final String FACTORY_JNDI_NAME =
        "java:/hibernate/HibernateFactory";

    protected AbstractDataAccessWrapper() {}

    public static final synchronized SessionFactory getFactory()
        throws HibernateException {
        Logger.LOG.debug("getFactory");

        if(FACTORY == null) {
            Logger.LOG.debug("makeFactory");

            try {

                Context context = new InitialContext();

                FACTORY = (SessionFactory)context.lookup(FACTORY_JNDI_NAME);

            } catch(NamingException ne) {
                Logger.LOG.warn("Not in JNDI context!", ne);
            }

            if(FACTORY == null) {

                Configuration cfg = new Configuration();

                cfg.configure();

                FACTORY = cfg.buildSessionFactory();
            }
        }

        return FACTORY;
    }

    protected Session makeSession()
                           throws HibernateException,
                                  DataException {
        Logger.LOG.debug("makeSession");

        SessionFactory factory = getFactory();

        Session session = factory.openSession();

        try {

            //Guard against stale JDBC connections and bad connection pools
            while(!session.isConnected() || session.connection()
                                                       .isClosed()) {
                Logger.LOG.warn("New session reconnect!");
                session.reconnect();

            }
        } catch(SQLException sqle) {
            throw new DataException(sqle);
        }

        return session;
    }
}

Code:
package com.foo.bar.data.access;

import com.foo.bar.common.Logger;
import com.foo.bar.data.exception.DataException;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.ReplicationMode;
import net.sf.hibernate.Session;
import net.sf.hibernate.Transaction;
import java.sql.SQLException;

public class DataAccessWrapperImpl
    extends AbstractDataAccessWrapper {

    private transient Session session = null;

    private transient Transaction transaction = null;

    public DataAccessWrapperImpl() {
        super();
    }

    public synchronized Session getDataAccessObject()
                                             throws DataException {
        Logger.LOG.debug("getDataAccessObject");

        try {

            if(session == null) {
                session = makeSession();
            }
        } catch(HibernateException he) {
            throw new DataException("Problem getting DAW", he);
        }

        return session;
    }

    public synchronized void beginTransaction()
                                       throws DataException {

        if((transaction == null) && (session != null)) {

            try {
                transaction = session.beginTransaction();
            } catch(HibernateException he) {
                throw new DataException(
                                        "Problem beginning hibernate transaction!",
                                        he
                                       );
            }
        } else {
            Logger.LOG.warn("Overlapping Begin Transactions!");
        }
    }

    /**
     * Description of the Method
     **/
    public synchronized void cleanupDataAccessObject() {

        if(session != null) {
            Logger.LOG.debug("BEGIN cleanupDataAccessObject");

            Session tmpSession = session;
            session = null;

            try {
                tmpSession.flush();
            } catch(HibernateException he) {
                Logger.LOG.warn("Problem flushing session in DAW", he);
            }

            try {
                tmpSession.disconnect();
            } catch(HibernateException he) {
                Logger.LOG.warn("Problem disconnecting session in DAW", he);
            }

            try {
                tmpSession.close();
            } catch(HibernateException he) {
                Logger.LOG.warn("Problem closing session in DAW", he);
            }
        }

        Logger.LOG.debug("COMPLETED cleanupDataAccessObject");
    }

    public synchronized void commitTransaction()
                                        throws DataException {

        if(transaction != null) {

            try {
                session.flush();

                transaction.commit();

                transaction = null;

                session.clear();

            } catch(HibernateException he) {
                throw new DataException(
                                        "Problem committing hibernate transaction!",
                                        he
                                       );
            }
        } else {
            throw new DataException("Overlapping Commit Transactions!");
        }
    }

    public synchronized void replicate(Object replicant)
                                throws DataException {

        if(session != null) {

            try {
                session.replicate(
                                  replicant,
                                  ReplicationMode.LATEST_VERSION
                                 );
            } catch(HibernateException he) {
                throw new DataException(
                                        "Problem replicating object: " +
                                        replicant,
                                        he
                                       );
            }
        } else {
            throw new DataException("Replicate attempted without session!");
        }
    }

    public synchronized void rollbackTransaction() {
        Logger.LOG.fatal("Rollback Transaction!");

        try {
            if(transaction != null) {
                transaction.rollback();
            }
        } catch(HibernateException he) {
            Logger.LOG.warn("Problem rolling back Hibernate transaction!", he);
        }

        transaction = null;

        session.clear();
    }

    public synchronized void save(Object object) throws DataException {

        if(session != null) {

            try {
                session.saveOrUpdate(object);
            } catch(HibernateException he) {
                throw new DataException("Problem saving object: " + object, he);
            }
        } else {
            throw new DataException("Save attempted without session!");
        }
    }
}


Typical usage in the business layer looks something like this:
Code:
DataAccessWrapper daw = null;

try {
    daw = DataAccess.getDataAccessWrapper();

    Session session = daw.getDataAccessObject();

    ...do stuff with session...
} catch(DataException de) {
    ....
} catch(HibernateException he) {
    throw new BusinessLogicException("FUBAR in business layer!", he);
} finally {
    if(daw != null) {
        daw.cleanupDataAccessObject();
    }
}


I have an abstract base for DataAccessWrapperImpl because I used to subclass it for different environments. (I used to have a version that implemented javax.servlet.http.HttpSessionBindingListener so it could clean itself up when the http session was removed. Don't do that anymore, though...)

This may be a bit of overkill, but I like abstracting my application layers as much as possible. I didn't go so far as to completely shield my business layer from Hibernate. That _would_ be overkill! But this accomplishes the maximum shielding with the minimum code, methinks. I don't think I'll ever move away from Hibernate. (It's just too frikkin' kewl!) But...I'm paranoid. :)


Top
 Profile  
 
 Post subject:
PostPosted: Mon Jan 12, 2004 8:09 pm 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
Check the Architecture Forum, There are some threads on similar subjects

_________________
Emmanuel


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 5 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.