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
Comments