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!