-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 4 posts ] 
Author Message
 Post subject: LazyInitializationException on evict()
PostPosted: Fri Jan 29, 2010 2:18 pm 
Newbie

Joined: Thu Dec 06, 2007 9:18 pm
Posts: 10
I have a parent entity with two lazy lists of child entities. To simplify searches for distinct entities, the parent entity has a 'hash' field which is a calculated value based on data in the parent and all of the children - when the 'getHash' for that field is called, the hash is recalculated (i.e. if the user changes a child's value, the parent hash value will change)

The mapping looks like this:

Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class name="Group" table="test_grouptable">
         <id name="id" unsaved-value="0">
            <generator class="increment"/>
        </id>

        <property name="hash"/>
       
        <list name="shortStories" cascade="all,delete-orphan" inverse="true" batch-size="10">
            <key column="parent_id" not-null="true"/>
            <index column="idx"/>
            <one-to-many class="ShortStory"/>
        </list>
       
        <list name="longStories" cascade="all,delete-orphan" inverse="true" batch-size="10">
            <key column="parent_id" not-null="true"/>
            <index column="idx"/>
            <one-to-many class="LongStory"/>
        </list>
        <property name="name" type="string"/>
    </class>
    <class name="Story" table="test_story" discriminator-value="-1"  batch-size="10">
         <id name="id" unsaved-value="0">
            <generator class="increment"/>
        </id>
        <discriminator column="type" type="int" force="true"/>
       
        <many-to-one name="parent" column="parent_id" not-null="true"/>
        <property name="idx" column="idx" type="int"/>
       
        <property name="info"/>
        <subclass name="LongStory" discriminator-value="0" />
        <subclass name="ShortStory" discriminator-value="1" />
       
    </class>
</hibernate-mapping>


If I load the parent and then (without accessing either of the children) evict it, the evict code is retrieving the values for all of the fields - and when it hits the getHash() method, getHash() fails with

Code:
Exception in thread "main" org.hibernate.PropertyAccessException: Exception occurred inside getter of Group.hash
        at org.hibernate.property.BasicPropertyAccessor$BasicGetter.get(BasicPropertyAccessor.java:172)
        at org.hibernate.tuple.entity.AbstractEntityTuplizer.getPropertyValues(AbstractEntityTuplizer.java:272)
        at org.hibernate.tuple.entity.PojoEntityTuplizer.getPropertyValues(PojoEntityTuplizer.java:244)
        at org.hibernate.persister.entity.AbstractEntityPersister.getPropertyValues(AbstractEntityPersister.java:3590)
        at org.hibernate.event.def.AbstractVisitor.process(AbstractVisitor.java:146)
        at org.hibernate.event.def.DefaultEvictEventListener.doEvict(DefaultEvictEventListener.java:111)
        at org.hibernate.event.def.DefaultEvictEventListener.onEvict(DefaultEvictEventListener.java:93)
        at org.hibernate.impl.SessionImpl.fireEvict(SessionImpl.java:980)
        at org.hibernate.impl.SessionImpl.evict(SessionImpl.java:972)
        at Main.main(Main.java:48)
Caused by: java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.hibernate.property.BasicPropertyAccessor$BasicGetter.get(BasicPropertyAccessor.java:169)
        ... 9 more
Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: Group.longStories, no session or session was closed
        at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
        at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372)
        at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:119)
        at org.hibernate.collection.PersistentList.size(PersistentList.java:114)


The code looks like this:

Code:
session = HibernateUtil.currentSession();
group = (Group)session.get(Group.class, 1);
session.evict(group);


One odd thing in here is that the failure is when the code tries to access the *second* collection - getHash() has already retrieved one of the lazy collections successfully before the above exception is thrown:


Code:
  public Integer getHash()
  {
      Integer hash = name.hashCode();
      System.out.println((shortStories == null ? "No short Stories" : "Has short stories"));
      if (shortStories != null) {
         System.out.println(shortStories.size() + " short stories");
         for (ShortStory item : shortStories) {
            hash += item.getInfo().hashCode();
            hash += new Integer(item.getIdx()).hashCode();
         }
      }            
      System.out.println((longStories == null ? "No long Stories" : "Has long stories"));
      if (longStories != null) {
Line 42 -->     System.out.println(longStories.size() + " long stories");
         for (LongStory item : longStories) {
            hash += item.getInfo().hashCode();
            hash += new Integer(item.getIdx()).hashCode();
         }
      }            
      
      return hash;
  }


If I initialize the children before calling session.evict(parent), then it all works (for example, by calling my parent.toString() which visits all of the children). If I call session.clear() instead of evict, it works - but that is rather heavy-handed, as there are times when I want to evict a subset of my entities rather than all of them.

Another down side (besides the exception being thrown...) is that it looks like evict() is going to visit all of the entities I wish to evict - which (due to the hash calculation for the child collections) is expensive - for example, if I just want to get the values a parent-defined field without loading the children, that isn't going to work efficiently any more as evict() is going to (unnecessarily) load every child.

So... Is there something I'm missing in my mapping or elsewhere that would affect that evict() behavior (ideally the visiting of every field in the entities but at least stopping the exception from being thrown)? Or is there some way I can tell (in my getHash() method) that the caller doesn't *really* need the most up-to-date hash value (i.e. the caller is doing an evict rather than a save/update/...) and I can safely skip the calculation?

The solution has to retain the connection between parent and child - the real code just loads and saves the parent entity - it depends on the mapping relationships to make sure any contained children are persisted when the parent is persisted. I would hate to have to change all of that code so it persisted each child individually rather than just the parent...

Hibernate: hibernate-distribution-3.3.2.GA
java: 1.6.0_15

Thanks!


Top
 Profile  
 
 Post subject: Re: LazyInitializationException on evict()
PostPosted: Tue Feb 02, 2010 7:20 pm 
Newbie

Joined: Thu Dec 06, 2007 9:18 pm
Posts: 10
After some debugging, the entity is being loaded twice and the old version has its session set to null - but the top level getHash() code is holding onto a reference of the old (uninitialized, sessionless) version, not the new (initialized) version.

Unsetting the session happens in StatefulPersistenceContext.java:

Code:
private void addCollection(PersistentCollection coll, CollectionEntry entry, Serializable key) {
      collectionEntries.put( coll, entry );
      CollectionKey collectionKey = new CollectionKey( entry.getLoadedPersister(), key, session.getEntityMode() );
      PersistentCollection old = ( PersistentCollection ) collectionsByKey.put( collectionKey, coll );
      if ( old != null ) {
         if ( old == coll ) {
            throw new AssertionFailure("bug adding collection twice");
         }
         // or should it actually throw an exception?
-->         old.unsetSession( session );
         collectionEntries.remove( old );
         // watch out for a case where old is still referenced
         // somewhere in the object graph! (which is a user error)
      }
   }


And, looking at the object graph, the 'watch out for a case...' statement is true. The getHash() method in Group has a copy of the child lists with their parent Group field set to 'Group_$$_javassist_2 (id=XX)'. When the first list in getHash() is accessed, Hibernate initializes both lists - which means the second list in the Group's 'this' loses its session (but is still has its group set to 'Group_$$_javassist_2 (id=XX)') so Hibernate a) thinks it is still uninitialized and b) can no longer initialize it.

So... does anyone have any tips on how to unravel the 'which is a user error' part of the 'watch out for ...' comment? I don't have a session object in my getHash(), so cannot initialize the lists manually (I think - or is there some way to figure out the session context a method is being called from within)? And, I'd rather (for an evict) not incur the cost of loading my child collections in the first place - but again, not sure how I would detect that condition vs a valid call (such as during a save or update call).

Thanks.


Top
 Profile  
 
 Post subject: Re: LazyInitializationException on evict()
PostPosted: Wed Feb 03, 2010 3:22 am 
Expert
Expert

Joined: Wed Mar 03, 2004 6:35 am
Posts: 1240
Location: Lund, Sweden
I would recommend that you simplify your code. The getHash()/setHash() methods are better off being as simple as possible. Eg.

Code:
private Integer hash;
public Integer getHash()
{
  return hash;
}
public void setHash(Integer hash)
{
  this.hash = hash;
}


Then, add a method, for example, calculateHash() that updates the hash value and that you can control yourself when it is called. I have been down a similar path as you and realized that it is never possible to know when or why Hibernate will call a setter/getter method in your mapped classes. It can happen often, and in your case I guess it could be quite expensive if the collections are large.


Top
 Profile  
 
 Post subject: Re: LazyInitializationException on evict()
PostPosted: Wed Feb 03, 2010 10:23 am 
Newbie

Joined: Thu Dec 06, 2007 9:18 pm
Posts: 10
Yes, I originally started down that path. The actual code is quite a bit more complex, and there are places where methods manipulate the child collections without having an instance of the parent class, so they cannot (currently) call caclulateHash() on the parent. Having the getHash() calculate the value solved that, as it gets called on the parent by Hibernate if the child entity is persisted.

The code has a few spots where the entities are created or updated, and lots of spots where they are read. The cost of doing the calculation is the same (ignoring the 'calculate on evict' issue) whether the calculation is initiated manually via a caclulateHash() method or automatically by Hibernate. But, since Hibernate seem to evaluate all fields on evict(), your 'it could be quite expensive' comment is applicable.

Ideally I would be able to evict without incurring that cost, but if that cannot be done I guess a manual calculation is the solution.

Thanks for your time!


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 4 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.