views:

1032

answers:

3

i have a forum with 1..n members each having 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"&gt;

<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 one fails 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 one 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 the way i load the member.

The hibernate reference says:

"The fetch strategy defined in the mapping document affects:

  1. retrieval via get() or load()
  2. retrieval that happens implicitly when an association is navigated
  3. Criteria queries
  4. HQL queries if subselect fetching is used"

The succeeding test is case 1. (retrieval via get() or load()) The failing test is case 2 IMHO

Why does "forum.getMembers().iterator().next()" not load all articels of the member?

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 do not understand it!

+1  A: 

The two cases are really quite different.

In the failing example, you're loading the Forum entity, which has a lazy-initialized collection of Member entities. When you try to navigate through the collection, it fails because the collection is a lazy one, and you didn't load the collection before the session was closed.

The working example, this isn't "graph navigation", you're loading the Member entity directly, and so there's no lazy-loading semantics involved.

The documentation that you're referring to involved different ways of phrasing the first case, where specifying eager loading on the set of Members would prevent the failure.

skaffman
I don't think that's true in this case, actually. He gets the "member" before `endTransaction()` is called, so presumably it's still in session. What fails is subsequent `member.getArticles().size()` call. The more puzzling issue is why the 2nd test succeeds; seeing as how `fetch="join"` should not override default `lazy="true"` setting it seems like it should have failed too. I'm going to guess it was previously loaded and initialized "member" that was obtained from the session.
ChssPly76
A: 

Are you using HQL in the findById(...) method? When using HQL, Hibernate doesn't respect your fetch/join settings; you need to use an HQL clause, like "left join fetch", to cause associations to be loaded eagerly. Also, if you are Hibernate 2.x, you will not be able to eagerly fetch more than one collection in a single query. See the Advanced Problems FAQ on the hibernate website:

https://www.hibernate.org/117.html#A13

RMorrisey
Please look at my **EDIT 1**: I do not use HQL but standard session methods from springs hibernateTemplate. I am aware of the "limitation" of HQL
Janning
Ok, sorry for not being much help =(
RMorrisey
A: 

I think it is a hibernate documentation bug.

In the reference documentation it says:

"On the other hand, you can use join fetching, which is non-lazy by nature"

In the JPwH book it says , p. 579

Hence, a fetch="join" disables lazy loading.

It is not true. When i change my articles mapping to

<set name="articles" fetch="join" lazy="false" table="articles" inverse="true">

both test run fine, but second test does not run a join when loading the member!

So my conclusion: Eager fetching with joins strategy does not affect proxy initalization nor does it disable lazy loading. This is in contrast to the hibernate documentation.

Janning
It's not that simple, the fetch/join behaviour depends on *how* you ask for the object, e.g. whether you use HQL, Criteria API, session.get() etc.
skaffman
The hibernate doc says that lazy loading is diabled with fetch join, so it should affect <i>when</i> the object is loaded too.But if you understand it.. could you explain why the grapgh is not loaded? I do not use HQL or Criteria Api. Just session.get() and navigating the object tree.
Janning