-->
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.  [ 2 posts ] 
Author Message
 Post subject: N+1 on fetch of one-to-one association?
PostPosted: Thu Mar 05, 2009 5:43 pm 
Newbie

Joined: Tue Mar 03, 2009 12:50 pm
Posts: 5
Location: New York
After reading all of "Java Persistence with Hibernate", reading the relevant chapters over and over again, searching online and through these posts, and trying all sorts of permutations on this, I'm convinced this is just a fundamental misunderstanding on my part, but anything you may be able to clear up will be greatly appreciated.

I have a one-to-one association between two classes. When I create a Query or Criteria on one of these classes with a FetchMode.JOIN on the other, my expectation when calling criteria.list() is that a single SQL statement against the database will be created, so that both the class and its association are loaded at once, with no further hits against db.

Instead, I see what looks like to me as an N+1 issue. That is, a single SQL query that appears to capture all the information for a list of both classes is run. Then, apparently redundantly, a whole series of identical one-row SQL queries get run, containing what appears to be a subset of the same information that the 1st query returns!

My goal is to get this so that only 1 query runs, to minimize database hit when retrieving lists of pre-initialized objects.

I've pared it down to the simplest case between two classes, which is why I think I must be missing something in understanding. Please see details below.

This post seems to be the same issue:
http://forum.hibernate.org/viewtopic.ph ... +redundant

If I find the answer myself, I'll be sure to update this post. But, I'd would definitely appreciate any help, as I've been banging my head against this one awhile.

Hibernate version:3.2.5

Mapping documents:

Class One, with a one-to-one association to class Two
Code:
@Entity
@Table(name="HTEST_ONE")
public class One
{
   @Id
   @GeneratedValue(strategy= GenerationType.AUTO)
   protected Long id;

   @OneToOne(fetch = FetchType.LAZY)
   @JoinColumn
   private Two two;

   @XmlTransient
   public Two getTwo()
   {
      return two;
   }

   public void setTwo(Two two)
   {
      this.two = two;
   }

}


Code:
@Entity
@Table(name="HTEST_TWO")
public class Two
{
   @Id
   @GeneratedValue(strategy= GenerationType.AUTO)
   protected Long id;

   @OneToOne(fetch = FetchType.LAZY, mappedBy = "two")
   protected One one;

   @XmlTransient
   public One getOne()
   {
      return one;
   }

   public void setOne(One one)
   {
      this.one = one;
   }
}


Code between sessionFactory.openSession() and session.close():

Code:
Criteria criteria = session.createCriteria(One.class)
                .setComment("criteria: simple")
                .createAlias("two","t", CriteriaSpecification.INNER_JOIN)
                .setFetchMode("t", FetchMode.JOIN)
                 ;
list = (List<One>) criteria.list();


Full stack trace of any exception that occurs:

No exceptions. Trying to optimize.

Name and version of the database you are using:

Oracle 11g

The generated SQL (show_sql=true):
/* criteria query: This gets run once */
select
this_.id as id4_1_,
this_.two_id as two2_4_1_,
t1_.id as id5_0_
from
HTEST_ONE this_,
HTEST_TWO t1_
where
this_.two_id=t1_.id

/* load hibernatebear.One -- After the above, this gets run immediately...
This query gets run many times, seeming like an N+1 problem. */
select
one0_.id as id4_0_,
one0_.two_id as two2_4_0_
from
HTEST_ONE one0_
where
one0_.two_id=?

All this happens before the Java code excerpted above (in "Code between sessionFactory.openSession() and session.close()") returns.

Debug level Hibernate log excerpt:

(for completeness, just an excerpt...)

16:01:46,323 INFO main MultiuseDao:15 - Creating HibernateTemplate
16:01:46,448 DEBUG main ConnectionManager:421 - opening JDBC connection
16:01:46,448 DEBUG main AbstractBatcher:366 - about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
16:01:46,463 DEBUG main SQL:401 -
/* criteria: simple */ /* criteria query */ select
this_.id as id4_1_,
this_.two_id as two2_4_1_,
t1_.id as id5_0_
from
HTEST_ONE this_,
HTEST_TWO t1_
where
this_.two_id=t1_.id
16:01:46,604 DEBUG main AbstractBatcher:382 - about to open ResultSet (open ResultSets: 0, globally: 0)
16:01:46,682 DEBUG main AbstractBatcher:389 - about to close ResultSet (open ResultSets: 1, globally: 1)
16:01:46,682 DEBUG main AbstractBatcher:374 - about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
16:01:46,682 DEBUG main AbstractBatcher:366 - about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
16:01:46,682 DEBUG main SQL:401 -
/* load hibernatebear.One */ select
one0_.id as id4_0_,
one0_.two_id as two2_4_0_
from
HTEST_ONE one0_
where
one0_.two_id=?
16:01:46,713 DEBUG main AbstractBatcher:382 - about to open ResultSet (open ResultSets: 0, globally: 0)
16:01:46,713 DEBUG main AbstractBatcher:389 - about to close ResultSet (open ResultSets: 1, globally: 1)
16:01:46,713 DEBUG main AbstractBatcher:374 - about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
16:01:46,713 DEBUG main AbstractBatcher:366 - about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
16:01:46,713 DEBUG main SQL:401 -
/* load hibernatebear.One */ select
one0_.id as id4_0_,
one0_.two_id as two2_4_0_
from
HTEST_ONE one0_
where
one0_.two_id=?
16:01:46,760 DEBUG main AbstractBatcher:382 - about to open ResultSet (open ResultSets: 0, globally: 0)
16:01:46,760 DEBUG main AbstractBatcher:389 - about to close ResultSet (open ResultSets: 1, globally: 1)
16:01:46,760 DEBUG main AbstractBatcher:374 - about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
16:01:46,760 DEBUG main AbstractBatcher:366 - about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
16:01:46,760 DEBUG main SQL:401 -
/* load hibernatebear.One */ select
one0_.id as id4_0_,
one0_.two_id as two2_4_0_
from
HTEST_ONE one0_
where
one0_.two_id=?
16:01:46,791 DEBUG main AbstractBatcher:382 - about to open ResultSet (open ResultSets: 0, globally: 0)
16:01:46,791 DEBUG main AbstractBatcher:389 - about to close ResultSet (open ResultSets: 1, globally: 1)
16:01:46,791 DEBUG main AbstractBatcher:374 - about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
16:01:46,791 DEBUG main AbstractBatcher:366 - about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
16:01:46,791 DEBUG main SQL:401 -
/* load hibernatebear.One */ select
one0_.id as id4_0_,
one0_.two_id as two2_4_0_
from
HTEST_ONE one0_
where
one0_.two_id=?
16:01:46,823 DEBUG main AbstractBatcher:382 - about to open ResultSet (open ResultSets: 0, globally: 0)
16:01:46,823 DEBUG main AbstractBatcher:389 - about to close ResultSet (open ResultSets: 1, globally: 1)
16:01:46,823 DEBUG main AbstractBatcher:374 - about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
16:01:46,823 DEBUG main AbstractBatcher:366 - about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
16:01:46,823 DEBUG main SQL:401 -
/* load hibernatebear.One */ select
one0_.id as id4_0_,
one0_.two_id as two2_4_0_
from
HTEST_ONE one0_
where
one0_.two_id=?


Top
 Profile  
 
 Post subject: a hint? Which side gets mappedBy makes a difference
PostPosted: Mon Mar 09, 2009 4:03 pm 
Newbie

Joined: Tue Mar 03, 2009 12:50 pm
Posts: 5
Location: New York
Very interesting... I finally figured out a fix for this. I hope this helps others...

In the example above, you will not get the N+1 problem if, instead of running:
Code:
Criteria criteria = session.createCriteria(One.class)
                .setFetchMode("two", FetchMode.JOIN)
                 ;
list = (List<One>) criteria.list()

Instead you run:
Code:
Criteria criteria = session.createCriteria(Two.class)
                .setFetchMode("one", FetchMode.JOIN)
                 ;
list = (List<Two>) criteria.list()


This appears to have something to do with which side of the OneToOne association you place your "mappedBy" (or @JoinColumn) annotation on.

One workaround I'm using is to simply add both sides of the fetch equation, like so...

Code:
Criteria criteria = session.createCriteria(One.class)
                .setFetchMode("two", FetchMode.JOIN)
                .setFetchMode("two.one", FetchMode.JOIN)
                 ;
list = (List<One>) criteria.list()


No N+1 with that! Perhaps someone else can explain the reasoning for this. Or... Should this be a issue be considered a bug?


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 2 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.