Wednesday 22 February 2012

Hibernate LazyInitialization Exception


Would you agree that loading only as little data as needed, a.k.a lazy loading, is a good practice? You probably  would. To prevent lengthy object graphs be loaded into memory Hibernate uses lazy loading by default. The downside is that sooner or later you are destined to see something like this:

org.hibernate.LazyInitializationException: could not initialize proxy - no Session

The problem arises when relationships are involved. For instance order and order items, person and addresses, employee and organisation etc. It is tempting to think that when an ORM provider turns data into an object you can freely navigate to any property you want. This is however not the case. The objects are managed by Hibernate and only the data from the primary table (orders, people and employees) is populated when lazy loading is on.

Before you resort to a popular (anti)pattern called Open Session in View or give up on lazy loading in general, consider the approach I would call a selective eager load.

I demonstrate this solution on a simple example powered by Spring. If you are tired of reading just download the source code and take a look yourself.

Let's say you deal with Person and Addresses, one-to-many relationship. As a fan of JPA I load personal details by using the EntityManager:
...
import javax.persistence.*;
...

    @PersistenceContext
    private EntityManager em;

    @Override
    public Person find(Long id) {
        return em.find(Person.class, id);
    }
...
No addresses are available at this stage and trying to access those results in the LazyInitializationException:
// Bad mistake, an exception is thrown
Collection<Address> addresses = person.getAddresses();
To be able to get to addresses they have to be fetched explicitly when asking for personal details:
public Person find(Long id) {
  String select = "select distinct p from Person p " +
                                   "left join fetch p.addresses " +
                                   "where p.id = :id";

  return (Person) em.createQuery(select)
                         .setParameter("id", id)
                         .getSingleResult();
}
If you prefer not to write queries yourself you can take advantage of the Hibernate's Criteria feature:
import javax.annotation.Resource;
import org.hibernate.FetchMode;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Restrictions;
import org.springframework.transaction.annotation.Transactional;
...
  @Resource(name="sessionFactory")
  private SessionFactory sessionFactory;

  @Transactional
  public Person find(Long id) {
    return (Person) sessionFactory
                     .getCurrentSession()
                     .createCriteria(Person.class)
                     .setFetchMode("addresses", FetchMode.JOIN)
                     .add(Restrictions.eq("id", id))
                     .uniqueResult();
  }
...
In order to use the feature the method has to be marked as transactional. Detailed explanations can be found here.

The Criteria object has become available in the JPA standard as of version 2.0. The initial example could therefore be rewritten:
...
  @PersistenceContext
  private EntityManager em;

  public Person find(Long id) {
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery query = cb.createQuery(Person.class);
             
    Root person = query.from(Person.class);
    person.fetch("addresses");
             
    Predicate condition = cb.equal(person.get("id"), id);
    query.where(condition);
                          
    return em.createQuery(query.select(person)).getSingleResult();
  }
...
As you can see there are many options of how to handle lazily loaded properties. Consider it as an advantage providing you with a better control of what data is loaded and when.

It is fair to say there are limitations though. The examples above do not lend themselves to pagination very well. Think query.setFirstResult(int) and query.setMaxResults(int). Due to one-to-many relationship the pagination has to be performed in memory rather than in a database.


Resources:

No comments:

Post a Comment