Search code examples
javahibernateh2lazy-initializationproxy-classes

LazyInitializationException, for an int


A simple class with an integer field:

@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@Table(name = "myObject")
public class MyObject 
{
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;

  @Column(columnDefinition = "int default 0")
  @Index(name = "refCount")
  private int refCount;

  public int getRefCount(){ return refCount; }
}

Objects are fetched from the database using a simple Utility method:

Session session = SessionFactoryUtil.getCurrentSession();
Transaction tx = session.beginTransaction();

criteria.setFetchSize(1);
T object = (T) criteria.uniqueResult();

// I tried to add this line, but it made no difference
Hibernate.initialize(object);

tx.commit();
return object;

The problem is the following: Shortly after fetching this object, I am calling the getRefCount method. At that point I encounter the following exception:

org.hibernate.LazyInitializationException: could not initialize proxy - no Session
    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:164)
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:285)
    at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:185)
    at mypackage.MyObject_$$_javassist_1.getRefCount(MyObject_$$_javassist_1.java)

My hibernate configuration file (i.e. hibernate.cfg.xml) contains the following property:

 <property name="hibernate.current_session_context_class">thread</property>

What I don't understand: If this would happen to a collection, then I would just add the fetch = FetchType.LAZY annotation. But this simple int field is not a join. Why would an int ever be wrapped inside a Proxy in the first place ?

I tried to add the Hibernate.initialize(object); line, but it made no difference at all.

I also experimented with the hibernate.current_session_context_class="managed" setting. After which I had to start and stop all sessions manually. I opened it at every fetch and closed it in a finally block. But that also made no difference.

This is one of my first Hibernate projects. I'm starting to wonder if I should open a transaction before calling getters on hibernate objects.

I'm not using Spring, just Hibernate.

EDIT: actually there is a 2nd object

Actually there is a parent object (which I initially thought was not important). This Parent object contains a link to the MyObject

@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@Table(name = "parentObject")
public class ParentObject 
{
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;

  // link
  @ManyToOne(optional = false, fetch = FetchType.LAZY)
  @ElementCollection(targetClass = MyObject.class)
  @JoinColumn(name = "myObjectId")
  private MyObject myObject;

  public MyObject getMyObject(){ return myObject; }
}

What happens is:

  1. a Parent object gets fetched
  2. parent.getMyObject() is called to get a MyObject instance
  3. This MyObject instance is in fact a proxy without any fields on it.
  4. As soon as I call a method on this MyObject instance, I get the LazyInitializationException

When I fetch my objects I make sure a session exists and a transaction is created. But after the fetching I immediately close the transaction.

I am not creating a transaction when I'm calling the getMyObject() or when calling the getters. I guess that's the problem. I'll test if that makes a difference.

EDIT 2:

It turns out that I indeed need to call the getters inside a transaction. But that in itself is not enough.

A second problem is that the Parent object was fetched in a transaction that was already committed. As a result, the proxy object is no longer bound to an event. I guess that's what they call a "detached object". (lol, I'm just learning as we go here.)

I had to "reattach" this object by calling the Session#update(proxy) method. Now finally I can call the getter without exceptions.

// uses a transaction internally
Parent parent = MyHibernateUtil.fetch(Parent.class, ...);
MyObject object = parent.getMyObject();

...

// create a new transaction
Session session = SessionFactoryUtil.getCurrentSession();
Transaction tx = session.beginTransaction();

// reattach the object
SessionFactory.getCurrentSession().update(myObject);

int count = myObject.getRefCount();
tx.commit();

But what I learned from this issue is that I probably use transactions the wrong way. I guess I should make longer transactions that contain both the fetches and the calls to the getters. Right ?


Solution

  • I suppose whole object(MyObject in your case) is proxied. Could you call getId instead of getRefCount() ?