LazyInitializationException overcome

In Hibernate, relationships can be defined so that they are "lazily initialized"; i.e., the relationship is only initialized when it is determined that the information contained in the additional table is needed. This is fine as long as it is determined that the information is needed within the current Session.

 

But, what if this is not true?

 

The "traditional" ways of handling this are to either open the Session in the view (which is a very un-MVC concept), get everything at once (forget about lazy loading - but this could end up getting everything), leave the Session open (IMnsHO, a very bad practice), or make sure that you get what you need initially. For the project I'm working on, these "alternatives" would not work. You see, in that project the EMPLOYEE table is lazily associated with the ASSIGNMENT table. This is because it is usual for the UI to present to the user a list of employees, and the user selects IN A DIFFERENT SESSION which employee he would like to see detail information on, which includes assignment information over time. But, how to do this? By the time the user decides which employee to get detail information on, the original Session is closed.

 

When presented with this situation, at first I tried to do all the heavy lifting in the getAssignments () method. But, I soon found out that any Hibernate methods called within a getter would end up making infinite recursive calls. This method is not perfect, but it's the best I could come up with.

 

There are three things that must be done. In your Hibernate session factory class, define the static methods "hasOpenSession" and "getSession" to get Session information directly from Hibernate. In your Employee class, add this code:

 

    /**
     * Determine initialization status of the assignments array.
     * @return boolean Initialized or not
     */
    public boolean isAssignmentsInit () {
        PersistentSet pss = (PersistentSet)assignments;
        return pss.wasInitialized ();
    }
   
    /**
     * Get the current object for this row.
     * @return Employee The current object representing a row.
     */
    public Employee getCurrent () {
        boolean hasOpen = HibernateSessionFactory.hasOpenSession ();
        Session sess = HibernateSessionFactory.getSession ();
        Employee current = (Employee)sess.get (Employee.class, this.getId ());
        if (current == null) {
            current = (Employee)sess.load (Employee.class, this.getId ());
        }
        if (!hasOpen) {
            // Initialize the assignments before closing the session
            current.assignments.size ();
            sess.close ();
        }
        return current;
    }

 

Note that the code above embraces the Hibernate PersistentSet concept, rather than rejecting it.

 

The first method, isAssignmentsInitialized, exists simply to tell the caller whether or not the relationship has been initialized. It matters not whether or not the original Session is still open.

 

The second method, getCurrent, is a bit more complex. This is because it handles 6 different situations:

1) The original Session is still open and the assignments array is initialized.

2) The original Session is still open and the assignments array is not initialized.

3) A different Session is open and the assignments array is initialized.

4) A different Session is open and the assignments array is not initialized.

5) No Session is open and the assignments array is initialized.

6) No Session is open and the assignments array is not initialized.

 

Let's go through line-by-line.

 

boolean hasOpen = HibernateSessionFactory.hasOpenSession ();

Determine if the case is 5) or 6).

 

Session sess = HibernateSessionFactory.getSession ();

Get the current or a new Session. You'll need this for the guts of this method.

 

Employee current = (Employee)sess.get (Employee.class, this.getId ());

Get the Employee object that is associated with the current Session. If it exists. If the original Session is open (cases 1) and 2)), this returns the current object. If a different Session is open (cases 3) and 4)) or no Session is open (cases 5), and 6)), this returns the currently associated object, if it exists. If it doesn't exist, null is returned. Then we have to do something different...

 

if (current == null) {
    current = (Employee)sess.load (Employee.class, this.getId ());
}

If the row is not associated with the current Session, we must load it from the database. Note that both the get and load calls take a Class object and a row ID.

 

if (!hasOpen) {

OK, if there was no open Session previously (cases 5) and 6)), there is some special handling involved.

 

current.assignments.size ();

Force initialization of the assignments array. If you don't do this, you'll keep on running into the same problem over and over and over again.

 

An aside: if your class contains more than one lazy relationship, you must initialize all of them here.

 

sess.close ();

This is why you had to initialize the lazy relationships in the last step. Cases 5) and 6) require a temporary Session - which you'll want to close before moving on.

 

Everywhere you call getAssignments (), you must use this code:

 

// Avoid Hibernate LazyInitialization errors like the plague
if (!ee.isAssignmentsInit ()) {
    ee = ee.getCurrent ();
}

where ee is an Employee. It is saying that, if the relationship hasn't been initialized, make sure you are working with the current object

 

Hope this helped. The LazyInitializationException can be extremely hard to overcome, and on the way you may run into other Hibernate errors. I thought I'd pass on my experience with this.

 

--Tim Sabin