Hibernate + ZK

From Documentation
Hibernate + ZK

Author
Henri Chen, Principal Engineer, Potix Corporation
Date
September 14, 2006
Version
Applicable to ZK 2.1.1 and later.
Applicable to Hibernate 3.1 and later.


The Purpose

The incompatiblity between Hibernate's single thread session management and ZK's mutil-thread event model has been a serious issue. It causes Hibernate's LazyInitializationExceptions in "Open Session In View" pattern and generates other surprises from time to time. Though Thomas Muller (Oberinspector) has proposed a new set of Hibernate utilities (see How-Tos) trying to solve these issues. However, it still got some limitations. The major issue is that developers are forced to give up the preferred factory.getCurrentSession() pattern to these new APIs and have no way to incorporate ZK into existing applications seamlessly.

To solve such limitations in one shot, the ZK team have worked out some solutions. In this smalltalk, we will dig into the details of the issues and the solution that will work with the Hibernate's thread SessionContext (ThreadLocalSessionContext). Some other solution that will work with the "OpenSessionInViewFilter" of the Spring Framework will be ready since ZK 2.1.2 version.


The Basic Concept: Hibernate Session

A Hibernate session is the way how Hibernate deals with grouping data accessing operations, the so called "Unit Of Work". All data accessing must be done completely in such a session or all things have to be rollbacked to they were and causing no unknown state or inconsistent data. Though a Hibernate session is not a database transaction, the details is out of the scope of this article. Basically, you can think that you need a Hibernate session to group all database accessing and that is all.


How is that done?

Following is a code snip taken from the Hibernate's Document "Transaction demarcation with plain JDBC":

try {
    factory.getCurrentSession().beginTransaction();

    // Do some work
    ...
    
    factory.getCurrentSession().load(...);
    factory.getCurrentSession().persist(...);

    factory.getCurrentSession().getTransaction().commit();
}
catch (RuntimeException e) {
    factory.getCurrentSession().getTransaction().rollback();
    throw e; // or display error message
}

The singleton created SessionFactory factory holds the OR-mapping meta information defined in Hibernate configuration. It is responsible for creating and holding the current Hibernate session. A "Unit Of Work" is scoped from get a new session until session.close(). All OR-mapped java objects created, deleted, read, and written inside this "Unit Of Work" would finally be flushed into the associated relational database in a whole or none at all.


The Issue

Hibernate works in any environment that uses JTA transaction manager. However, for lightweight servlet engine (e.g. Tomcat) that gets no built-in JTA support, Hibernate provides a thread-bound strategy that you can enable it in the Hibernate configuration:

<hibernate-configuration>
    ...
    <session-factory>
        ...

        <!-- Hibernate's transaction factory -->
        <property name="transaction.factory_class">
        org.hibernate.transaction.JDBCTransactionFactory
        </property>

        <!-- Bind the getCurrentSession() method to the thread. -->
        <property name="current_session_context_class">thread</property>
        
        ...
    </session-factory>
</hibernate-configuration>


To carry such "current session" around without the help of JTA transaction manager, a generic pattern is to save it into the current thread's ThreadLocal variable. Hibernate's ThreadLocalSessionContext implementation does the same thing. It creates and binds the current Hibernate session to the ThreadLocal variable of the current Java thread when factory.getCurrentSession() is first called (create-on-demand pattern). And it will close the current session automatically after the session transaction is committed or rollbacked. This strategy works perfectly in single thread programming model while it will generate surprises in multi-threads environment such as ZK.


The ZK Multi-Threads Environment

Besides the servlet thread created by the servlet engine for each regular http request and/or Ajax XMLHttpRequest, ZK will create a new event thread for each handled event. This is where the issue comes from. In such multi-threads environment, Hibernate might not access to the expected "current session" that was created and bound to the ThreadLocal variable in previous thread and would create an "unwanted" new session and bound to the ThreadLocal variable in this thread (because it thought getCurrentSession() was first called).


The Solution

Thanks to Thomas Muller (Oberinspector) for his contribution. In ZK's How-Tos he has proposed a new set of Hibernate utilities to create, store, and get the current Hibernate Session from ZK's Session and thus avoid multi-thread environment issue. However, this solution still got some limitations:

  • Developers have to provide ZK's Session each time to get the current Hibernate session. It is not as convenient as the classic factory.getCurrentSession() pattern.
  • There might be no way to rewrite codes for some existing applications which has been written in classic Hibernate APIs and want to incorporate ZK.
  • Impossible to work seamlessly with the existing Hibernate related utilities (e.g. some "Open Session In View" filter that utilize the factory.getCurrentSession() pattern).


To solve all above limitations in one shot, we have written this ZK listener, HibernateSessionContextListener in zkplus.jar, which intercepts the ExecutionInit, ExecutionCleanup, EventThreadInit, and EventThreadResume. Basically, it copys the servlet thread bound sessionMap (the map that is used by Hibernate's ThreadLocalSessionContext to hold the current Hibernate session) to event thread's ThreadLocal variable whenever a new event thread is initiated or resumed. The new way is much more straitforward and works seamlessly with existing Hibernate applications as long as it follows Hibernate's factory.getCurrentSession() pattern. All you have to do is simply put the following lines inside the application's WEB-INF/zk.xml and it is done.

<listener>
    <description>Hibernate thread session context handler</description>
    <listener-class>
    org.zkoss.zkplus.hibernate.HibernateSessionContextListener
    </listener-class>
</listener>


The HibernateSessionContextListener codes

Here we will walk the listener codes so you can follow the concept to handle other issues also related to the ThreadLocal variables and multi-threads environment in ZK.

In ExecutionInit.init(), which running in servelet thread, the code first checks whether a Hibernate sessionMap has been bound to the ThreadLocal variable. If a sessionMap has already been bound there (maybe by an "Open Session In View" filter), then store it to ZK's Execution attribute. If no sessionMap has been bound to the servlet thread's ThreadLocal yet, then create a sessionMap and bind it to the ThreadLocal of the servlet thread and store it into Execution attribute. The intention is to always hold the sessionMap in Execution attribute such that we can reference and copy it over to the event thread later.

public void init(Execution exec, Execution parent) {
    //always prepare a ThreadLocal SessionMap in Execution attribute
    Map map = getSessionMap();
    if (map == null) { //not bounded to the ThreadLocal yet
        map = new HashMap();
        setSessionMap(map); //prepare one and bound to the ThreadLocal
    }
    exec.setAttribute(HIBERNATE_SESSION_MAP, map); //store into Execution attribute
}


The EventThreadInit.init() and EventThreadResume.afterResume() code, which running in event thread, are the same and simple. It copys the sessionMap previously stored in Exectuion attribute to ThreadLocal of the event thread. Then, no matter running in servlet thread or event thread, factory.getCurrentSession() would always found the same Hibernate sessionMap and get the same held "current Hibernate session" and it is the same "Unit Of Work".

public void init(Component comp, Event evt) {
    //Copy SessionMap stored in Execution attribute into event's ThreadLocal
    Map map = (Map) Executions.getCurrent().getAttribute(HIBERNATE_SESSION_MAP);
    setSessionMap(map); //copy to event thread's ThreadLocal
}


The "Event" Example

We have used the Hibernate's tutorial "Event" as our testing example.

The "Event" java object:

package events;

import java.util.Date;

public class Event {
    private Long id;

    private String title;
    private Date date;

    public Event() {}

    public Long getId() {
        return id;
    }
    private void setId(Long id) {
        this.id = id;
    }
    public Date getDate() {
        return date;
    }
    public void setDate(Date date) {
        this.date = date;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
}


The "Event" OR-mappig configuration file, Event.hbm.xml:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
        
<hibernate-mapping>

    <class name="events.Event" table="EVENTS">
        <id name="id" column="EVENT_ID">
            <generator class="native"/>
        </id>
        <property name="date" type="timestamp" column="EVENT_DATE"/>
        <property name="title"/>
    </class>

</hibernate-mapping>


The "EventDAO" that doing the data accessing job:

package events;

import org.zkoss.zkplus.hibernate.*;

import java.util.Date;
import java.util.List;

public class EventDAO {
    org.hibernate.Session _session;

    public EventDAO() {
        _session = HibernateUtil.getSessionFactory().getCurrentSession();
    }
    
    public void saveOrUpdate(Event anEvent, String title, Date date) {
        anEvent.setTitle(title);
        anEvent.setDate(date);
        
        _session.saveOrUpdate(anEvent);
    }

    public void delete(Event anEvent) {
        _session.delete(anEvent);
    }
    
    public Event findById(Long id) {
        return (Event) _session.load(Event.class, id);
    }
    
    public List findAll() {
        return _session.createQuery("from Event").list();
    }
}


Notice that to demostrate the cross thread capability of the new ZK listener, we don't start the Hibernate session nor commit the data in EventDAO. We will use the so called "OpenSessionInView" pattern instead. For each Execution, a Hibernate session would be automatically opened at the begin and closed at the end.

Following is how to add ZK's OpenSessionInViewListener in the application's WEB-INF/zk.xml.

<listener>
    <description>Hibernate Open Session In View lifecycle</description>
    <listener-class>org.zkoss.zkplus.hibernate.OpenSessionInViewListener</listener-class>
</listener>

The OpenSessionInViewListener is implemented as an ExecutionInit and ExecutionCleanup listener. It begins a session when ExecutionInit and close it when ExecutionCleanup.

In fact, you do not need to use this OpenSessionInViewListener provided by ZK. You can also use a servlet filter to do the same thing as long as it use the factory.getCurrentSession() pattern. They will work seamlessly with the new HibernateSessionContextListener.

The original Hibernate example is not a web application and is tested as a Java program. To make it work as a web application, we added a zuml page as the accessing page. The page is layout as an "Event" management page with current "Event" list on the left side and three buttons, add, edit, and delete on the right side. End users can press the button to add, edit, or delete an "Event", respectively.

  • Press the add button would pop up a dialog (modal window) that user can key in the title and the date then press OK to insert it into database or press Cancel to ignore the operation.
  • Press the edit button would edit the current selected "Event" and show it on the pop up dialog that user can modify the title or the date then press OK to update the event or press Cancel to ignore the modification.
  • Press the delete button would pop up a confirmation Messagebox asking whether the user is sure to delete the selected "Event". If the end user press Yes, the Event would be deleted. If press No, the delete operation would be canceled.

Hibernatezk.gif


To test the example:

  • Download the hsqldb database engine
  • unzip it to a directory, say c:\hsqldb.
  • Play around

The Conclusion

ZK has provided enough "interception" points that developers can use them to bridge the gap between the ThreadLocal programming model and ZK's multi-threads environment. The example demonstrated in this smalltalk has shown the elegance of this approach. Simply configure the HibernateSessionContextListener into the application's WEB-INF/zk.xml and you are done.

<listener>
    <description>Hibernate thread session context handler</description>
    <listener-class>org.zkoss.zkplus.hibernate.HibernateSessionContextListener</listener-class>
</listener>

We hope you enjoy these important new features.





Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.