ZK/How-Tos/Integrate-Other-Frameworks/HowToHandleHibernateSessions

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Preliminary!

My description of HibernateSessionHandling is based on a Hibernate/Tapestry configuration described in Enjoying Web Development with Tapestry... I have adapted this to ZK with a set of classes to provide hibernate session support with almost no configuration.

There is also a Spring/Hibernate solution for handling hibernate sessions, which works with a servelet filter. Spring offers also a lot more benefits (i know only from hearsay...) for hibernate and is probably a better solution... i don't know whether the Spring hibernate session handling could also be used for the ZK Ajax request/response cycle... i heard somebody is working on a description...


Assumptions[edit | edit source]

the main rules for hibernate sessions in web applications (in my opinion... please correct me...):

  • a Hibernate sessionfactory is an expensive resource... it's thread safe and there should be only one instance per Web application
  • a Hibernate session is an inexpensive and non-threadsafe resource.
  • a Hibernate session can be seen as a single piece of work. There should be only one per HttpSession
  • a hibernate session and transaction should normally be closed when the control is back to the client
  • creating a database connection is a time consuming process... use a connection pool!
  • don't access detached Hibernate POJOs (plain old Java objects) after a session is closed, since related data might not be retrieved from the database (lazy initialisation)!
  • ...

How this solution works[edit | edit source]

The very first time a hibernate session is needed in the application a Hibernate session is requested with the call of static final HibernateSessionProvider.getHibernateSession(HttpSession) (@tomyeh... how to provide a zul shortcut???). This checks the attributes of the current HttpSession for a HibernateSessionOwner... when not available it creates one and stores it in the current HTTPSession. The constructor of HibernateSessionOwner checks the WebApp attributes for an attribute HibernateSessionCreator()... when not available it creates one and stores it in the attributes of the WebApp... so we have one instance of the HibernateSessionCreator in the web application, which is a wrapper for a hibernate session factory, which is used to create hibernate sessions... When the HibernateSessionOwner and -Creator are instantiated the getHibernateSession() asks the HibernateSessionOwner for an Hibernate session... SessionOwner gets it from the creator and manages it further on... The HibernateSessionOwner holds the session and provides it every time the session is needed again in the ongoing thread... To cleanup the Hibernate session when the thread ends, the HibernateSessionEventThreadCleanup class is used (and configured in web.xml), which implements the zk interface EventThreadCleanup... in the cleanup routine the threadDidDiscardService() of the HibernateSessionOwner of the current session is called, stops the transaction and closes the hibernate session...

Configuration[edit | edit source]

Configure a jdbc connection pool in Tomcats server.xml:

...
<Context docBase="TKTestWeb" 
         path="/TZTestWeb" 
         reloadable="true" 
         source="org.eclipse.jst.j2ee.server:ZKTest">
   
    <Resource auth="Container" 
              defaultAutoCommit="false" 
              driverClassName="com.mysql.jdbc.Driver" 
              maxActive="20" name="jdbc/zktest" 
              password="testpw" 
              timeBetweenEvictionRunsMillis="60000" 
              type="javax.sql.DataSource" 
              url="jdbc:mysql://localhost:3306/zktest" 
              username="testuser"/>
</Context>
...

referr to jdbc datasource in web.xml:

...
<resource-ref>
    <res-ref-name>jdbc/infratour</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
</resource-ref>
...

configure EventThreadCleanup in zk.xml:

<zk>
   <listener>
     <listener-class>com.potix.hibernate.HibernateSessionEventThreadCleanup</listener-class>
   </listener>
</zk>

configure a SessionFilter in web.xml ??? TODO: provide a class which does the same as EventThreadCleanup for normal HttpSession request/response cycles

Java Sources[edit | edit source]

Add the following java classes to your clathpath (WEB-INF/classes)

HibernateSessionProvider.java:

import org.hibernate.Session;
import com.potix.util.logging.Log;
import com.potix.zk.ui.http.SessionImpl; 


public class HibernateSessionProvider {
	private static final Log log = Log.lookup(HibernateSessionProvider.class);
	private static final String HIBERNATE_SESSION_OWNER = "de.test.web.services.DefaultSessionOwner";

	//TODO add some debug logging
	public static final Session getHibernateSession(SessionImpl httpSession){
		
		Session hs;
		HibernateSessionOwner sessionOwner = (HibernateSessionOwner)httpSession.getAttribute(HIBERNATE_SESSION_OWNER);
		
		if(sessionOwner==null){
			
			sessionOwner = new HibernateSessionOwner(httpSession);
			httpSession.setAttribute(HIBERNATE_SESSION_OWNER, sessionOwner );

	      }
 		return hs;
	}
}

HibernateSessionOwner.java:

import org.hibernate.Session;
import org.hibernate.Transaction;
import com.potix.util.logging.Log;
import com.potix.zk.ui.http.SessionImpl;

public class HibernateSessionOwner implements SessionOwner {
	private static final String HIBERNATE_SESSION_CREATOR = "de.test.web.services.DefaultSessionCreator";
 	private static final Log log = Log.lookup(HibernateSessionCreator.class);
 
	private SessionCreator creator;
  	private Session hibernateSession;
 	private Transaction tx;
	private boolean isToRollback;
	//TODO add some debug logging
	public HibernateSessionOwner(SessionImpl httpSession) {
		creator = (SessionCreator)httpSession.getWebApp()
		          .getAttribute(HIBERNATE_SESSION_CREATOR);
 		
		if(creator == null){
			httpSession.getWebApp()
			.setAttribute(HIBERNATE_SESSION_CREATOR, new HibernateSessionCreator());
			creator = (SessionCreator)httpSession
					.getWebApp()
	          			.getAttribute(HIBERNATE_SESSION_CREATOR);
			if(creator == null){
				log.error("Could not install SessionCreatorService" );
				throw new RuntimeException("Could not install SessionCreatorService");
			}
		}
	}
	
	public Session getSession() {
		if (hibernateSession == null) {
			hibernateSession = creator.createSession();
			if (tx == null) {
				tx = hibernateSession.beginTransaction();
				isToRollback = false;
			}
		}
		return hibernateSession;
	}

	public void threadDidDiscardService() {
		if (hibernateSession != null) {
			try {
				endTransaction();
			} finally {
				hibernateSession.close();
				hibernateSession = null;
			}
		}
	}

	public void setToRollback() {
		isToRollback = true;
	}

	public void endTransaction() {
		if (tx != null) {
			try {
				if (isToRollback) {
					tx.rollback();
				} else {
					tx.commit();
				}
			} catch (RuntimeException e) {
				tx.rollback();
				throw e;
			} finally {
				tx = null;
			}
		}
	}

	public void setSessionCreator(SessionCreator creator) {
		this.creator = creator;
	}
}


HibernateSessionCreator.java:

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

import com.potix.util.logging.Log;

public class HibernateSessionCreator implements SessionCreator {
	private static String CONFIG_FILE_LOCATION = "/hibernate.cfg.xml";
	/** A session attribute. */
	
 	private static final Log log = Log.lookup(HibernateSessionCreator.class); 
	
	private SessionFactory sessionFactory;

	public HibernateSessionCreator() {
		Configuration cfg = new Configuration();
		cfg.configure(CONFIG_FILE_LOCATION);
		sessionFactory = cfg.buildSessionFactory();

		if (sessionFactory == null){
			log.error("Could not crate SessionFactory" );
			throw new RuntimeException("Could not crate SessionFactory");
		}
	}

	public Session createSession() {
		return sessionFactory.openSession();
	}
}

HibernateSessionEventThreadCleanup.java:

import com.potix.zk.ui.Component;
import com.potix.zk.ui.event.Event;
import com.potix.zk.ui.event.EventThreadCleanup;

public class HibernateSessionEventThreadCleanup implements EventThreadCleanup{
	private static final String HIBERNATE_SESSION_OWNER = "de.infratour.web.services.DefaultSessionOwner";
 
	public void cleanup(Component comp, Event event) {
		if(event.getPage().getSession().getAttribute(HIBERNATE_SESSION_OWNER)==null)
			return;

		((HibernateSessionOwner)event.getPage().getSession()
					.getAttribute(HIBERNATE_SESSION_OWNER))
					.threadDidDiscardService();
	}
}


HibernateSessionHttpRequestCleanup.java:

TODO!!!

External Libs[edit | edit source]

Add the following jars from the hibernate distribution to your classpath (WEB-INF/lib) :

  • hibernate3.jar
  • ...

Working with the Hibernate session[edit | edit source]

With the above-mentioned steps ZK is hibernate enabled... until now you can plug in your hibernate configuration and use it in your *.zul templates...

Add your hibernate POJO classes with the Hibernate mapping and configuration files to the classpath:

WEB-INF/lib
+-*myhibernate.jar
  +-*package
  | +-*Person.class
  | +-*Person.hbm.xml
  *hibernate.cfg.xml

Registration form example:

<window title="Registration" border="normal">

	
 	<zscript>{
	    
	    import de.infratour.hbm.*;
	    import de.infratour.web.services.HibernateSessionProvider;
	    import org.hibernate.Session;
	    
		void submit() {
		
			Person person = new Person();

			person.setNickname(nickname.value);
			person.setEmail(email.value);
			person.setForename(forename.value);
			person.setSurename(surename.value);
			
			if(password1.value!=password2.value){
					alert("The repeat password is different from password! please enter again!");
					password1.value="";
					password2.value="";
					return;
					//TODO ...mark textbox red ???
			}
			person.setPasswd(password1.value);
			
			try {
				org.hibernate.Session hs = (org.hibernate.Session)HibernateSessionProvider.getHibernateSession(session);
				hs.persist(person);
			}catch (Exception e) {
				alert(e.getMessage());
			}
		}		
	}
	</zscript>
	
	<grid>
	<rows>
		<row>Name : <textbox id="forename" /></row>
		<row>Surename : <textbox id="surename"  /></row>
		<row>Login Name : <textbox id="nickname" /></row>
		<row>Email: <textbox id="email"  /></row>
		<row>Password : <textbox id="password1" type="password"/></row>
		<row>Repeat Password : <textbox id="password2" type="password"/></row>

		<row><button label="submit" onClick="submit()"/></row>
 	</rows>
	</grid>
</window>

Open Points[edit | edit source]

  • Provide a shortcut to static final routines of HibernateSessionProvider (@Tom... HowTo?)
  • Only one Thread per desktop... what about Hibernate session for multiple browser windows, but same HttpSession?
  • Don't flush a session after Exception occurred... want to handle Exceptions derived from database constrains (e.g. unique...) "nickname already assigned"...
  • ...