| <?xml version="1.0"?> |
| <!-- |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| --> |
| |
| <document> |
| |
| <properties> |
| <title>Hibernate Howto</title> |
| </properties> |
| |
| <body> |
| |
| <section name="Introduction"> |
| <p> |
| This is a HOWTO on implementing Hibernate as the database OM layer. The motivating factors for using Hibernate |
| are: |
| <ol> |
| <li> |
| You just use POJO (Plain Old Java Objects) versus generated objects. |
| </li> |
| <li> |
| 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. |
| </li> |
| <li> |
| Extensive performance optimizations built into the Hibernate engine. |
| </li> |
| <li> |
| Leverage Avalon component use in Turbine 2.3! |
| </li> |
| </ol> |
| </p> |
| <p> |
| Please review the content available from the Hibernate homepage. This howto assumes |
| you already are confortable with the various Hibernate concepts. |
| </p> |
| <p> |
| 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. |
| <ol> |
| <li> |
| Configure project dependencies. |
| </li> |
| <li> |
| Directory Structures |
| </li> |
| |
| <li> |
| Setup Avalon component configuration. |
| </li> |
| |
| <li> |
| Setup Hibernate mapping files. |
| </li> |
| <li> |
| Create static ServiceLocator class. |
| </li> |
| <li> |
| Create filter that manages the Hibernate Session. |
| </li> |
| <li> |
| Setup PersitenceException handling |
| </li> |
| </ol> |
| </p> |
| |
| </section> |
| |
| <section name="Directory Structures"> |
| <p> |
| While you can organize your directory structure however you like, I tend to have structure like: |
| </p> |
| <source> |
| /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 |
| |
| </source> |
| |
| </section> |
| |
| <section name="Configure project dependencies" > |
| <p> |
| The easiest way to maintain a project is to use Maven. Here are the sample |
| dependencies for your project.xml. |
| </p> |
| <source> |
| <![CDATA[ |
| <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> |
| ]]> |
| </source> |
| <p> |
| 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. |
| </p> |
| |
| </section> |
| |
| <section name="Setting up Avalon Configuration" > |
| |
| <p> |
| 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. |
| </p> |
| <source> |
| <![CDATA[ |
| |
| <my-system> |
| <component |
| role="net.sf.hibernate.avalon.HibernateService" |
| class="net.sf.hibernate.avalon.HibernateServiceImpl"> |
| </component> |
| </my-system> |
| |
| ]]> |
| </source> |
| |
| <p> |
| The componentRoles file also defines how to access the component. Here is a |
| very simple componentRoles.xml file: |
| </p> |
| <source> |
| <![CDATA[ |
| |
| <role-list> |
| <role |
| name="net.sf.hibernate.avalon.HibernateService" |
| shorthand="hibernate" |
| default-class="net.sf.hibernate.avalon.HibernateServiceImpl"/> |
| </role-list> |
| |
| ]]> |
| </source> |
| </section> |
| |
| |
| |
| <section name="Setup Hibernate mapping files"> |
| <p> |
| 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. |
| </p> |
| <p> |
| 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. |
| </p> |
| </section> |
| |
| |
| |
| <section name="Create static ServiceLocator class."> |
| <p> |
| 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. |
| </p> |
| |
| <source> |
| <![CDATA[ |
| 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."); |
| |
| } |
| } |
| } |
| ]]> |
| </source> |
| </section> |
| |
| <section name="Create Servlet Filter."> |
| <p> |
| 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? |
| |
| </p> |
| |
| <source> |
| <![CDATA[ |
| 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); |
| } |
| |
| } |
| } |
| |
| ]]> |
| </source> |
| </section> |
| |
| |
| |
| |
| |
| |
| <section name="Implementing Manager classes"> |
| <p> |
| To centralize access to the database, we create Manager classes. Often Manager |
| classes are called DAO (Data Access Classes). Here is an example: |
| </p> |
| <source> |
| <![CDATA[ |
| /* |
| * 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; |
| |
| } |
| |
| |
| } |
| |
| ]]> |
| </source> |
| <p> |
| 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! |
| </p> |
| </section> |
| |
| <section name="Setup PersitenceException handling"> |
| <p> |
| 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. |
| </p> |
| <source> |
| <![CDATA[ |
| 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); |
| } |
| |
| } |
| |
| ]]> |
| </source> |
| |
| </section> |
| </body> |
| </document> |
| |
| |