Java – Lazy loading property and session.get problem

hibernatejavalazy-loading

In Hibernate we have two classes with the following classes with JPA mapping:

package com.example.hibernate

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;

@Entity
public class Foo {
  private long id;
  private Bar bar;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }

  @ManyToOne(fetch = FetchType.LAZY)
  public Bar getBar() {
    return bar;
  }

  public void setBar(Bar bar) {
    this.bar = bar;
  }
}

package com.example.hibernate

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;


public class Bar {
  private long id;
  private String title;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }


  public String getTitle() {
    return title;
  }

  public void setTitle(String title) {
    this.title = title;
  } 
}

Now when we load from the database an object from class Foo using session get e.g:

Foo foo = (Foo)session.get(Foo.class, 1 /* or some other id that exists in the DB*/);
the Bar member of foo is a proxy object (in our case javassist proxy but it can be cglib one depending on the bytecode provider you use), that is not initialized.
If you then use session.get to fetch the Bar object that is the member of the Foo class just loaded (we are in the same session), Hibernate does not issue another DB query and fetches the object from the session (first level) cache. The problem is this is a proxy to Bar class which is not initialized and trying to call this object getId() will return 0, and getTitle() will return null.
Our current solution is pretty ugly and checks if the object returned from get is a proxy here is the code (form a generic DAO implementation):

@SuppressWarnings("unchecked")
@Override
@Transactional(readOnly = true)
public <T extends IEntity> T get(Class<T> clazz, Serializable primaryKey) throws DataAccessException {
  T entity = (T) currentSession().get(clazz, primaryKey);
  if (entity != null) {
    if (LOG.isWarnEnabled()) {
      LOG.warn("Object not found for class " + clazz.getName() + " with primary key " + primaryKey);
    }
  } else if (entity instanceof HibernateProxy){ // TODO: force initialization due to Hibernate bug
    HibernateProxy proxy = (HibernateProxy)entity;
    if (!Hibernate.isInitialized(proxy)) {
      Hibernate.initialize(proxy);
    }
    entity = (T)proxy.getHibernateLazyInitializer().getImplementation();
  }
  return entity;
}

Is there a better way to do this, couldn't find a solution in the Hibernate forum, and didn't find the issue in Hibernate's JIRA.

Note: we cannot just use foo.getBar() (which will initialize the proxy properly) to get the Bar class object, because the session.get operation to fetch the Bar object does not know (or care for that matter) that the Bar class is also a lazy member of a Foo object that was just fetched.

Best Answer

I had a similar problem:

  • I did Session.save(nastyItem) to save an object into the Session. However, I did not fill in the property buyer which is mapped as update="false" insert="false" (this happens a lot when you have a composed primary key, then you map the many-to-one's as insert="false" update="false")
  • I a query to load a list of items, and the item which I just saved, happens to be part of the result set
  • now what goes wrong? Hibernate sees that the item was already in the cache, and Hibernate does not replace (probably not to break my earlier reference nastyItem) it with the newly loaded value, but uses MY nastyItem I have put into the Session cache myself. Even worse, now the lazy loading of the buyer is broken: it contains null.

To avoid these Session issues, I always do a flush and a clear after a save, merge, update or delete. Having to solve these nasty problems takes too much of my time.