Update: this workaround is not a solution, just delaying the problem. I’m currently looking for other possibilities, databinder project looks promising.
I’ve been struggling with the infamous Lazy loading problem – LazyInitializationException and other related exceptions, such as “Could not initialize proxy – the owning Session was closed”. My current project uses Hibernate> ORM framework for persistence, simple Java in the business model layer and Wicket in the web layer. I chose to postpone learning the Spring framework because the task seemed simple enough to be solved without it, however I consider using it for the future projects. As the progress went, I started to get more and more Lazy-loading exceptions for objects that could not be initialized or saved. If you are reading this, you probably know what I’m talking about.
To my understanding, it is not the biggest problem that a Hibernate session is not open at the time when a lazy-loaded object is accessed. The Open Session In View solution to this problem exists and is easily implemented. It guarantees that a session is open during the web request processing lifetime. One can rely on the filter to start a transaction whenever a request begins and commit it after all processing (including the view rendering part) is over.
However, it is more important to keep in mind: even if there is an open session, any lazy object that was previously loaded in another session can not use it automatically. It must be THE same session that loaded the object, or the object needs to be reattached with a merge() or update() call. It doesn’t help if the getCurrentSession() method of your Hibernate session factory returns a session instance, or if the parent/children of the lazy-loaded object have been reattached using update(), the session mapping does not propagate recursively.
Solutions found on the web either suggest not to use lazy loading at all, or reattach the objects to the current session. The former takes lots of RAM to keep the whole object hierarchy loaded from the database and generates a crapton of SQL queries to do so. Reattaching may help in emergency cases but later it backfires by leaving desperate update method calls all over the code.
Yet another solution
In this example I will not use the Open Session in View method. Instead, the Hibernate session is opened when the user logs in. The Hibernate session is stored as a HTTP session attribute until the user either logs out or the session expires. More details on storing parameters in a HTTP session can be found at http://www.javabeat.net/javabeat/scwcd/tutorials/basics/5-session-handling.php. The most important goal here is to get informed when the HTTP session ends so that the Hibernate session can be closed too – the HibernateSessionAttributeValue handles that. The explicit log out is easier to handle by a link onClick() method. The crucial code is given below.
First, build a simple class for attaching a Hibernate session to a HTTP session. By implementing HttpSessionBindingListener interface, it asks for the session management to call its valueUnbound() method when the HTTP session expires.
Disclaimer: the code has not been tested properly, use on your own risk. Comments and suggestions are appreciated
class HibernateSessionAttributeValue implements HttpSessionBindingListener { protected org.hibernate.Session hibernateSession = null; public HibernateSessionAttributeValue(org.hibernate.Session hibernateSession) { super(); this.hibernateSession = hibernateSession; } public org.hibernate.Session getHibernateSession() { return hibernateSession; } public void valueBound(HttpSessionBindingEvent arg0) { } }
public void valueUnbound(HttpSessionBindingEvent arg0) { if (hibernateSession != null) { HibernateUtil.commit(hibernateSession); HibernateUtil.close(hibernateSession); setHibernateSession(null); } }
WiaSession.java
public class WiaSession extends WebSession { private static final String HIBERNATE_ATTR_NAME = "hibernate"; public static WiaSession get() { return (WiaSession) Session.get(); } public org.hibernate.Session getHibernateSession() { HibernateSessionAttributeValue sessionAttribute = (HibernateSessionAttributeValue) getAttribute(HIBERNATE_ATTR_NAME); if (sessionAttribute != null) { return sessionAttribute.getHibernateSession(); } return null; } // called from Login form public User login(String username, String password) throws Exception { HibernateSessionAttributeValue sessionAttribute = (HibernateSessionAttributeValue) getAttribute(HIBERNATE_ATTR_NAME); if (sessionAttribute != null) { throw new Exception("Session is already established!"); } // an open session is needed by User.get method org.hibernate.Session hibernateSession = HibernateUtil.startSession(); sessionAttribute = new HibernateSessionAttributeValue(hibernateSession); setAttribute(HIBERNATE_ATTR_NAME, sessionAttribute); user = User.get(username, password); if (user == null) { //failed - close the session and remove session attribute HibernateUtil.close(hibernateSession); setAttribute(HIBERNATE_ATTR_NAME, null); return null; } return user; }
//called by 'log out' link public void logout() { HibernateSessionAttributeValue sessionAttribute = (HibernateSessionAttributeValue) getAttribute(HIBERNATE_ATTR_NAME); if (sessionAttribute != null) { org.hibernate.Session hibernateSession = sessionAttribute.getHibernateSession(); if (hibernateSession != null) { HibernateUtil.commit(hibernateSession); HibernateUtil.close(hibernateSession); sessionAttribute.setHibernateSession(null); } } Session.get().invalidate(); }
WicketApplication.java
The main Wicket application class does not have any special handling except for creating WiaSession instances.
public class WicketApplication extends WebApplication { public WicketApplication() { } @Override protected void init() { super.init(); // causes the home page to be displayed instead of the default // "return to home page" link getApplicationSettings().setPageExpiredErrorPage(getHomePage()); } // get default home page @Override public Class<extends Page> getHomePage() { return CommonPage.class; } @Override public Session newSession(Request request, Response response) { WiaSession wiaSession = new WiaSession(request); return wiaSession; } }
How did this approach work out for you?
Unfortunately, I switched to another project soon after this was written, so it was not tested well