Hibernate ignores fetch = "join" on collection when navigating through the object tree using an iterator
I have a forum with 1..n members, each with 1..n articles, so this is my mapping:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping auto-import="true">
<class name="Forum" table="forum">
<id name="id">
<generator class="identity" />
</id>
<property name="name" />
<set name="members" table="members" inverse="true">
<key column="forum_id" not-null="true" />
<one-to-many class="Member" />
</set>
</class>
<class name="Article" table="article">
<id name="id">
<generator class="identity" />
</id>
<property name="title" />
<many-to-one name="member" column="member_id" class="Member"
not-null="true">
</many-to-one>
</class>
<class name="Member" table="member">
<id name="id">
<generator class="identity" />
</id>
<property name="name" />
<many-to-one name="forum" column="forum_id" class="Forum"
not-null="true">
</many-to-one>
<set name="articles" fetch="join" table="articles"
inverse="true">
<key column="member_id" not-null="true" />
<one-to-many class="Article" />
</set>
</class>
</hibernate-mapping>
now i have two tests:
this crash with LazyInitializationException:
public void testFetchJoinByIteratorNavigation ( )
{
Forum forum = (Forum) repository.findById(Forum.class, forumId);
Member member = forum.getMembers().iterator().next();
assertEquals(member.getName(), "firstMember");
endTransaction();
assertEquals(1, member.getArticles().size());
}
this success succeeds :
public void testFetchJoinByGraphNavigation ( )
{
Member member = (Member) repository.findById(Member.class, memberId);
assertEquals(member.getName(), "firstMember");
endTransaction();
assertEquals(1, member.getArticles().size());
}
The only difference is how I load the element.
Hibernate says:
"The sampling strategy defined in the matching document affects:
- search via get () or load ()
- which happens implicitly when navigating the association.
- Criteria queries
- HQL queries if subheading fetch is used
Next test is case 1. (search via get () or load ()) Sample test is case 2 IMHO
Why doesn't "forum.getMembers (). Iterator (). Next ()" load all member articles?
EDIT 1:
this is my repository:
package hibernate.fetch;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
public class Repository extends HibernateDaoSupport
{
public Object findById ( Class clazz, Long objectId )
{
return getHibernateTemplate().load(clazz, objectId);
}
public void save ( Object object )
{
getHibernateTemplate().saveOrUpdate(object);
}
}
this is my pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>hibernate-fetch</groupId>
<artifactId>hibernate-fetch</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<configuration>
<downloadSources>true</downloadSources>
<ajdtVersion>1.5</ajdtVersion>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>3.3.1.GA</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.8.0.GA</version>
</dependency>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>8.2-504.jdbc3</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.13</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>2.5.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jmock</groupId>
<artifactId>jmock</artifactId>
<version>1.1.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Edit 2: This is my test case:
package hibernate.fetch;
import org.springframework.test.AbstractTransactionalSpringContextTests;
public class ForumTest extends AbstractTransactionalSpringContextTests
{
private Repository repository;
private Long memberId;
private Long forumId;
private String name = "test";
@Override
protected String[] getConfigLocations ( )
{
return new String[] { "applicationContext.xml" };
}
@Override
protected void onSetUpBeforeTransaction ( ) throws Exception
{
System.out.println(">> preparing Test");
Forum forum = new Forum();
forum.setName(name);
repository.save(forum);
forumId = forum.getId();
Member member = new Member();
member.setName(name);
forum.addMember(member);
repository.save(member);
memberId = member.getId();
Article article = new Article();
article.setTitle(name);
member.addArticle(article);
repository.save(article);
super.onSetUpBeforeTransaction();
System.out.println(">> Test prepared");
}
public void testFetchJoinByGraphNavigation ( )
{
System.out.println(">> testFetchJoinByGraphNavigation");
Member member = (Member) repository.findById(Member.class, memberId);
assertEquals(member.getName(), name);
endTransaction();
assertEquals(1, member.getArticles().size());
}
public void testFetchJoinByIteratorNavigation ( )
{
System.out.println(">> testFetchJoinByIterationNavigation");
Forum forum = (Forum) repository.findById(Forum.class, forumId);
Member member = forum.getMembers().iterator().next();
assertEquals(member.getName(), name);
endTransaction();
// throws LazyInitializationException because articles were NOT loaded
assertEquals(1, member.getArticles().size());
}
public Repository getRepository ( )
{
return repository;
}
public void setRepository ( Repository repository )
{
this.repository = repository;
}
}
I really don't get it!
source to share
I think this is a hibernation documentation bug.
The reference documentation says:
"Alternatively, you can use join fetch, which is not inherently lazy."
JPwH book says: p. 579
Hence fetch = "join" disables lazy loading.
This is not true. When I change the display of articles to
<set name="articles" fetch="join" lazy="false" table="articles" inverse="true">
both test runs are fine, but the second test doesn't start the connection on item load!
So my takeaway is that fetching passionately with a pooling strategy does not affect proxy activation, nor does it disable lazy loading. This contrasts with the hibernate documentation.
source to share
The two cases are really very different.
In a bad example, you load a Forum object that has a lazily initialized collection of member objects. When you try to traverse the collection, it fails because the collection is lazy and you haven't loaded the collection until the session is closed.
Working example: this is not "graphical navigation", you are loading the Member object directly, and therefore there is no lazy loading semantics.
The documentation you are referring to has different ways of phrasing the first case where specifying the desired load on a set of members will prevent the crash.
source to share
Are you using HQL in the findById (...) method? When using HQL, Hibernate ignores your fetch / join settings; you need to use an HQL clause like "left join fetch" to make the associations look eagerly loaded. Also, if you are Hibernate 2.x you cannot eagerly get more than one collection in a single request. See the Frequently Asked Questions on the hibernation website:
source to share