Introduction
This is a HOWTO on implementing Hibernate as the database OM layer. The motivating factors for using Hibernate are:
- You just use POJO (Plain Old Java Objects) versus generated objects.
- You can either start with Java objects and create your database schema from there, or start with a database schema, and map the Java objects to the schema.
- Extensive performance optimizations built into the Hibernate engine.
- Leverage Avalon component use in Turbine 2.3!
Please review the content available from the Hibernate homepage. This howto assumes you already are confortable with the various Hibernate concepts.
In this example, we will take a simple schema with a single table and integrate Hibernate into a Turbine action. We will talk about best practices for Hibernate and Turbine.
- Configure project dependencies.
- Directory Structures
- Setup Avalon component configuration.
- Setup Hibernate mapping files.
- Create static ServiceLocator class.
- Create filter that manages the Hibernate Session.
- Setup PersitenceException handling
Directory Structures
While you can organize your directory structure however you like, I tend to have structure like:
/src/java/com/my/project/bizobj business objects reside /src/java/com/my/project/om POJO objects mapped to database reside /src/java/com/my/project/om/persist manager objects that faciliate retrieve POJO objects /src/java/com/my/project/om/filter Servlet Filter lives that maintains the Hibernate Session /src/java/ where hibernate.hbm.xml and hibernate.cfg.xml files live
Configure project dependencies
The easiest way to maintain a project is to use Maven. Here are the sample dependencies for your project.xml.
<dependency> <id>hibernate</id> <version>2.0-beta6</version> <properties> <war.bundle.jar>true</war.bundle.jar> </properties> </dependency> <dependency> <id>hibernate:hibernate-avalon</id> <version>0.1</version> <properties> <war.bundle.jar>true</war.bundle.jar> </properties> </dependency> <dependency> <id>dom4j</id> <version>1.4</version> <properties> <war.bundle.jar>true</war.bundle.jar> </properties> </dependency> <dependency> <id>cglib</id> <version>rc2-1.0</version> <properties> <war.bundle.jar>true</war.bundle.jar> </properties> </dependency> <dependency> <id>jcs</id> <version>1.0-dev</version> <properties> <war.bundle.jar>true</war.bundle.jar> </properties> </dependency>
The key jar file is the hibernate-avalon.jar file, it contains the Avalon wrapper for Hibernate, and can be used with any Avalon container.
Setting up Avalon Configuration
We leverage the Avalon based HibernateService that is hosted as part of the HibernateExt project. You must specify the name of the service, as well as the implementing service in your componentConfiguration.xml file. Other configuration parameters can be passed in as well.
<my-system> <component role="net.sf.hibernate.avalon.HibernateService" class="net.sf.hibernate.avalon.HibernateServiceImpl"> </component> </my-system>
The componentRoles file also defines how to access the component. Here is a very simple componentRoles.xml file:
<role-list> <role name="net.sf.hibernate.avalon.HibernateService" shorthand="hibernate" default-class="net.sf.hibernate.avalon.HibernateServiceImpl"/> </role-list>
Setup Hibernate mapping files
This section will not go into depth on how to configure the hibernate.cfg.xml and hibernate.hbm.xml files. However, if you don't specify in the componentConfiguration.xml file where they are located, then they should be placed in your src/java/ directory.
The exact configuration is dependent on the environment Turbine is running in, however I highly recommend that you use the JNDI look up of the datasource, and use DBCP for your connection pooling. I have found that when using Hibernate in cactus tests, you may need to increase the pool size required in order to not run out of connections, however this doesn't seem to apply to running as a web application.
Create static ServiceLocator class.
Currently this class is not integrated into Turbine, so you will need to provide an implemation yourself. This provides a static method of retrieving your Hibernate Session.
package com.upstate.cellculture.om.persist; import net.sf.hibernate.HibernateException; import net.sf.hibernate.JDBCException; import net.sf.hibernate.Session; import net.sf.hibernate.SessionFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * This class is used to get Hibernate Sessions and may * also contain methods (in the future) to get DBConnections * or Transactions from JNDI. */ public class ServiceLocator { //~ Static fields/initializers ============================================= public final static String SESSION_FACTORY = "hibernate/sessionFactory"; public static final ThreadLocal session = new ThreadLocal(); private static SessionFactory sf = null; private static ServiceLocator me; private static Log log = LogFactory.getLog(ServiceLocator.class); static { try { me = new ServiceLocator(); } catch (Exception e) { log.fatal("Error occurred initializing ServiceLocator"); e.printStackTrace(); } } //~ Constructors =========================================================== private ServiceLocator() throws HibernateException, JDBCException {} //~ Methods ================================================================ public static Session currentSession() throws PersistenceException { Session s = (Session) session.get(); if (s == null) { s = PersistenceManager.openSession(); if (log.isDebugEnabled()) { log.debug("Opened hibernate session."); } session.set(s); } return s; } public static void closeSession() throws HibernateException, JDBCException { Session s = (Session) session.get(); session.set(null); if (s != null) { if (s.isOpen()) { s.flush(); s.close(); if (log.isDebugEnabled()) { log.debug("Closed hibernate session."); } } } else { log.warn("Hibernate session was inadvertently already closed."); } } }
Create Servlet Filter.
The servlet Filter is required if you use lazy loading of objects because you need to open a Hibernate sesion, and keeps it open through the view layer. This ensures that each user get's their own Hibernate Session. ?? Should this be done through some sort of SessionValidator?
package com.upstate.cellculture.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import net.sf.hibernate.Session; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.upstate.cellculture.om.persist.PersistenceException; import com.upstate.cellculture.om.persist.ServiceLocator; public class ActionFilter implements Filter { //~ Static fields/initializers ============================================= //~ Instance fields ======================================================== /** * The <code>Log</code> instance for this class */ private Log log = LogFactory.getLog(ActionFilter.class); private FilterConfig filterConfig = null; //~ Methods ================================================================ public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; } /** * Destroys the filter. */ public void destroy() { filterConfig = null; } public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { // cast to the types I want to use HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; HttpSession session = request.getSession(true); Session ses = null; boolean sessionCreated = false; try { chain.doFilter(request, response); } finally { try { ServiceLocator.closeSession(); } catch (Exception exc) { log.error("Error closing hibernate session.", exc); exc.printStackTrace(); } } } public static Session getSession() throws PersistenceException { try { return ServiceLocator.currentSession(); } catch (Exception e) { throw new PersistenceException("Could not find current Hibernate session.", e); } } }
Implementing Manager classes
To centralize access to the database, we create Manager classes. Often Manager classes are called DAO (Data Access Classes). Here is an example:
/* * Created on Apr 28, 2003 * */ package com.upstate.cellculture.om.persist; import java.util.List; import net.sf.hibernate.Query; import net.sf.hibernate.Session; import com.upstate.cellculture.om.Technician; /** * @author tmckinney * * Centralizes all access to the Technicians table */ public class TechnicianManager { private static List allTechnicians; public static void save(Technician technician) throws PersistenceException { try { ServiceLocator.currentSession().save(technician); } catch (Exception e) { throw new PersistenceException("Could not save.", e); } } public static Technician retrieveByPK(long technicianId) throws PersistenceException { try { Technician technician= (Technician) ServiceLocator.currentSession().load(Technician.class, new Long(technicianId)); return technician; } catch (Exception e) { throw new PersistenceException("Could not retrieve.", e); } } public static List retrieveAllTechnicians() throws PersistenceException { if (allTechnicians == null) { try { Query q = ServiceLocator.currentSession().createQuery("from technician in class " + Technician.class +" order by upper(technician.name)"); allTechnicians = q.list(); session.flush(); } catch (Exception e) { e.printStackTrace(); throw new PersistenceException(e); } } return allTechnicians; } }
By using this TechnicianManager class, we could feasibly swap out the Hibernate code for another OM strategy. Even better would be to have a TechnicianManager interface and a TechicianManagerImpl class. Possibly loaded via Avalon as a component!
Setup PersitenceException handling
By catching a generice Persistence Exception that wraps all the thrown exceptions we can just catch a single exception and then retrieve what type of exception it is.
package com.upstate.cellculture.om.persist; import org.apache.commons.lang.exception.NestableException; /** * A general PersistenceException that is thrown by all Manager classes. * */ public class PersistenceException extends NestableException { //~ Constructors =========================================================== /** * Constructor for PersistenceException. */ public PersistenceException() { super(); } /** * Constructor for PersistenceException. * * @param message */ public PersistenceException(String message) { super(message); } /** * Constructor for PersistenceException. * * @param message * @param cause */ public PersistenceException(String message, Throwable cause) { super(message, cause); } /** * Constructor for PersistenceException. * * @param cause */ public PersistenceException(Throwable cause) { super(cause); } }