Lazy initialization, a Hibernate problem

There is a popular exception which a lot of Hibernate newbees encounter. In order to prevent this exception you need to understand the concept of Lazy Initialization.

1) When Hibernate reads data from the database, the data is hold in the session. You can save references to the data - for example in your HTTP request. Once the transaction is committed and the session is closed you can not load any further data with this session.

In the first example, we have seen that Honey has a collection of Bee. If we load a Honey instance, Hibernate will not load the bees automatically. There is a good reason for this behaviour. Imagine you load a Company and get all orders, order details and articles. Basically you load most of your database just by loading a single Company object. This would result into a memory problem. Therefore Hibernate loads only the first object and replaces collections of other objects by a proxy. If you access the proxy, Hibernate uses the current session to initialize the proxy and load the entries from the database.

2) Imagine a Struts framework application. If you do not know Struts: it is a framework to develop web applications. When a user requests something, e.g. he has submitted a form on a website, the following happens:

What is the consequence of 1) and 2)? When you look through the process 2) you can see that the session is already closed, when the dialogue is rendered. Your application logic has finished processing. If you have not initialized any objects while your session is open, you will not be able to display them. Have a look on the following diagram, which explains the situations quite well. \newline

When you access a not initialized object you will get a LazyInitializationException explaining that the session is already closed.

images/c_architecture_sequence_diagram.jpg

When can this happen? I have mentioned that Hibernate can map relations. Imagine a class department having a number of teams.

public class Department {
   private Integer id;
   private String name;

   private Set teams = new HashSet();

If you want to output a list of departments and teams in your JSP, you must not only fetch all instances of department but also all instances of teams which are associated with one of the departments you are retrieving.

I told you that by default all relations are retrieved lazy. This means when you fetch a department, Hibernate will not fetch the teams but create a proxy. When you access a team, the proxy uses the current session to load the team from the database. A proxy can only retrieve data when the session is open.

Having relations in your mapping you must ensure that the object and related objects are initialized as long as the session is open.

There are three solutions to this problem:

The first solution is dangerous. Imagine a relation like

ApplicationUser → KeyAccounter → Customer → Company → all customers of company → all orders of customers

Every access would load the complete database. Be very careful when you set lazy to false.

The second solution is simple but have some caveats. The trick is named Open-Session-In-View and is explained in chapter Open Session in View the section called “Lifetime until the view is rendered (Open-Session-in-View)”.

The third solution initializes the data before closing the session.

We have two options to initialize data.

Approach a)

List honeys = session.createQuery("select h from Honey as h").list();
for (Iterator iter = honeys.iterator(); iter.hasNext();) {
   Honey element = (Honey) iter.next();
   log.debug(element);
   Hibernate.initialize(element.getBees());

Approach b)

honeys = session.createQuery("select h from Honey as h left join fetch h.bees")
         .list()

If you use approach a) you have to call to call Hibernate.initialize on each proxy. Each call will generate one query.

Approach b) generates a left join statement. We will only require one query.

Consider to use b) if you query a lot of data.

You must be aware of a draw back of this approach. Left join results in double entries for Invoice when there are multiple orders. Think of the underlying sql query, which leads to a result like

invoice 1, joined order line 1
invoice 1, joined order line 2
invoice 2, joined order line 1
.....

Hibernate will as well add the invoice 1 multiple times to the result list.

You can use the following approach to get unique invoices (have a look in the HQL and Criteria Query chapter for detailed examples):

session.createCriteria(Honey.class).setFetchMode("bees",FetchMode.JOIN)
  .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).list();

session.createQuery("select h from Honey as h join fetch h.bees")
  .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).list();