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.
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:
Parent
object gets fetchedparent.getMyObject()
is called to get a MyObject
instanceMyObject
instance is in fact a proxy without any fields on it.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.
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 ?
I suppose whole object(MyObject in your case) is proxied. Could you call getId instead of getRefCount() ?