-->
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.  [ 3 posts ] 
Author Message
 Post subject: Bidirectional One-to-one association n+1 select problem
PostPosted: Fri Oct 23, 2009 5:12 am 
Newbie

Joined: Wed Oct 21, 2009 5:34 pm
Posts: 3
I've been trying to map a one-to-one association and I am still running into the problem of n+1 selects even after switching to nhibernate 2.1.0.

I'm sorry this post is so long but it seems to be the only way to get this issue across.

What i'm trying to implement is a one to one between a person and a customer. Person in the system may or may not be customers. But each customer must be related to a customer

Code:
class Person
{
    string firstname;
    ....
    Customer customer;
}

class Customer
{
    string Number
    Customer(Person person) { this.person = person; }
    ...
    Person person;
}

I defined the mappings as follows

Code:
<class name="Person" table="Person" >
    <id name="Id">
    <property name="FirstName"/>
    ...
    <one-to-one name="Customer" class="Customer" property-ref="Person"/>   
</class>

<class name="Customer" table="Customer">
    <id name="Id"/>
    <property name="Number"/>
    ...
    <many-to-one class="Person" name="Person" column="PersonID" not-null="true"
         unique="true"/>
</class>

Everything seems ok when I query from the <one-to-one> end

Code:
Person person = session.Get<Person>(1);
Console.WriteLine("Firstname={0}, Lastname={1}", person.FirstName, person.LastName);
if (tope.Customer != null)
{
       Console.WriteLine("After Get");
       Console.WriteLine("Customer Number={0}", person.Customer.CustomerNumber);
}

The output is as follows
Code:
SELECT person0_.id as id3_1_, person0_.Version as Version3_1_, person0_.FirstName as FirstName3_1_, person0_.LastName as LastName3_1_, customer1_.id as id0_0_, customer1_.Version as Version0_0_, customer1_.Number as Number3_0_0_, customer1_.PersonID as PersonID0_0_ FROM Tests.dbo.Person person0_ left outer join Tests.dbo.customer customer1_ on person0_.id=customer1_.PersonID WHERE person0_.id=@p0;@p0 = 1
Firstname=John, Lastname=Doe
After Get
Number=1111

The customer is always eagerly loaded no matter what the settings are on the <one-to-one>.
The only thing that can be changed is to stop it being fetched by an outer join by including outer-join="false" or fetch="select" on the <one-to-one> mapping.

I don't mind the outer join really since it dosen't result in an extra select query for the customer, the customer object is completely hydrated. Even navigating person.Customer.Person... does not result in an extra query. Please note that constrained="true" does not apply here because not every person object is a customer as well, but even setting constrained="true" does not seem to change anything.

Meanwhile I thought lazy loading <one-to-one> associations was one of the issues resolved by version 2.1 of nhibernate and version 3 of hibernate. This was actually my reason for migrating from version 2.0 to 2.1. Please correct me if i'm wrong on this. What can be done to make the <one-to-one> load the related object lazily, i.e. with a separate query only when the association is accessed.

The main problem is however the <many-to-one> end of the association. It seems this always results in a redundant query no matter what I do. I have tried various combinations of settings in the mapping files. I have also tried specifying all kinds of "..join fetch.." in hql and also using SetfetchMode in the criteria queries bu nothing seems to work. There is always a redundant query. This can get really bad when trying to fetch all customers. A separate query is run for each row in the customers record. This is a very basic requirement i.e. fetching all customers and getting the person as well which from a logical viewpoint suggests a simple join but it ends up being horribly ineffective. Right now I just so confused about all the options I've tried and I'm really frustrated with something that seems so simple. I'm not even sure I had this problem in version 2.0.

Examples of querying the <many-to-one> end

Code:
{
    Customer customer = session.Get<Customer>(4);
    Console.WriteLine("Before assert");

    Assert.IsNotNullcustomer.Person);

    Console.WriteLine("After assert, about to access person property");

    Assert.IsNotNull(customer.Person.FirstName);
}

Code:
SELECT customer0_.id as id0_0_, customer0_.Version as Version0_0_, customer0_.Number as Number3_0_0_, customer0_.PersonID as PersonID0_0_ FROM Tests.dbo.customer customer0_ WHERE customer0_.id=@p0;@p0 = 4
Before assert
After assert, about to access person property
SELECT person0_.id as id3_1_, person0_.Version as Version3_1_, person0_.FirstName as FirstName3_1_, person0_.LastName as LastName3_1_, person0_.Gender as Gender3_1_, customer1_.id as id0_0_, customer1_.Version as Version0_0_, customer1_.Number as Number3_0_0_, customer1_.PersonID as PersonID0_0_ FROM Tests.dbo.Person person0_ left outer join Tests.dbo.customer customer1_ on person0_.id=customer1_.PersonID WHERE person0_.id=@p0;@p0 = 1
SELECT customer0_.id as id0_0_, customer0_.Version as Version0_0_, customer0_.Number as Number3_0_0_, customer0_.PersonID as PersonID0_0_ FROM Tests.dbo.customer customer0_ WHERE customer0_.PersonID=@p0;@p0 = 1

Three queries are actually run here. The first one for the Customer details the next for the person in this fetched customer, and the third for the customer in the person just fetched. I am surprised that this is happening since a separate query was not issued in the previous scenario which fetched the customer for a person using a join. In fact an outer join is actually used here as well but it appears that the customer property of the person is not set this time. Adding constrained="true" also does not change this query.

The only improvement I seem to be able to make is to reduce the queries to two by adding fetch="join" to the <many-to-one> or doing a "join fetch" in a hql query or setting a FetchMode in a criteria query

Changing the many-to-one mapping to:
Code:
<many-to-one class="Person" name="Person" column="PersonID" not-null="true" unique="true" fetch="join"/>

results in:

Code:
SELECT customer0_.id as id0_1_, customer0_.Version as Version0_1_, customer0_.Number as Number3_0_1_, customer0_.PersonID as PersonID0_1_, person1_.id as id3_0_, person1_.Version as Version3_0_, person1_.FirstName as FirstName3_0_, person1_.LastName as LastName3_0_ FROM Tests.dbo.customer customer0_ inner join Tests.dbo.Person person1_ on customer0_.PersonID=person1_.id WHERE customer0_.id=@p0;@p0 = 4
SELECT customer0_.id as id0_1_, customer0_.Version as Version0_1_, customer0_.Number as Number3_0_1_, customer0_.PersonID as PersonID0_1_, person1_.id as id3_0_, person1_.Version as Version3_0_, person1_.FirstName as FirstName3_0_, person1_.LastName as LastName3_0_ FROM Tests.dbo.customer customer0_ inner join Tests.dbo.Person person1_ on customer0_.PersonID=person1_.id WHERE customer0_.PersonID=@p0;@p0 = 1
Before assert
After assert, about to access person property
After accessing person property


This time just two queries both of which fetch the customer and join the person also fetching the person's properties at the same time.

The two queries are both run eagerly so all the writes to console come after the queries.
The two queries are almost exactly alike. But why is the second redundant query required at all.

when I use the "join fetch" in hql query similar results as well

Code:
session.CreateQuery("select c from Customer c join fetch c.Person where c.id = 4").UniqueResult<Customer>();


Code:
select customer0_.id as id0_0_, person1_.id as id3_1_, customer0_.Version as Version0_0_, customer0_.Number as Number3_0_0_, customer0_.PersonID as PersonID0_0_, person1_.Version as Version3_1_, person1_.FirstName as FirstName3_1_, person1_.LastName as LastName3_1_ from Tests.dbo.customer customer0_ inner join Tests.dbo.Person person1_ on customer0_.PersonID=person1_.id where customer0_.id=4
SELECT customer0_.id as id0_0_, customer0_.Version as Version0_0_, customer0_.Number as Number3_0_0_, customer0_.PersonID as PersonID0_0_ FROM Tests.dbo.customer customer0_ WHERE customer0_.PersonID=@p0;@p0 = 1
Before assert
After assert, about to access person property
After accessing person property

Is there any solution to this problem?
What am I doing wrong?

This problem is far worse when I try to fetch all customers.
One select is done to retrieve all customer information and joining the person information.
Then multiple selects are issued, one for each customer to retrieve customer information for each person in the first query results

Code:
public void FetchAllCustomers()
{
    IList<Customer> extras = session.CreateQuery
        ("select c from Customer c join fetch c.Person").List<Customer>();
    Console.WriteLine("After fetching customers");
    foreach (Customer customer in customers)
        Console.WriteLine("{0}, {1}", customer.Number, customer.Person.FirstName);
}


Code:
select customer0_.id as id0_0_, person1_.id as id3_1_, customer0_.Version as Version0_0_, customer0_.Number as Number3_0_0_, customer0_.PersonID as PersonID0_0_, person1_.Version as Version3_1_, person1_.FirstName as FirstName3_1_, person1_.LastName as LastName3_1_ from Tests.dbo.customer customer0_ inner join Tests.dbo.Person person1_ on customer0_.PersonID=person1_.id
SELECT customer0_.id as id0_0_, customer0_.Version as Version0_0_, customer0_.Number as Number3_0_0_, customer0_.PersonID as PersonID0_0_ FROM Tests.dbo.customer customer0_ WHERE customer0_.PersonID=@p0;@p0 = 1
SELECT customer0_.id as id0_0_, customer0_.Version as Version0_0_, customer0_.Number as Number3_0_0_, customer0_.PersonID as PersonID0_0_ FROM Tests.dbo.customer customer0_ WHERE customer0_.PersonID=@p0;@p0 = 2
SELECT customer0_.id as id0_0_, customer0_.Version as Version0_0_, customer0_.Number as Number3_0_0_, customer0_.PersonID as PersonID0_0_ FROM Tests.dbo.customer customer0_ WHERE customer0_.PersonID=@p0;@p0 = 3
After fetching customers
1111, John
2222, Mike
3333, Peter

Please somebody save me.


Top
 Profile  
 
 Post subject: Re: Bidirectional One-to-one association n+1 select problem
PostPosted: Sat Apr 17, 2010 5:11 pm 
Beginner
Beginner

Joined: Thu Jul 31, 2008 8:28 pm
Posts: 24
Hi,
I seem to have the same problem with one-to-one relation cause N+1 selects.
I can't seem to find any documentation about this issue.
Any thing will be helpful.

Thank you,
Ido.


Top
 Profile  
 
 Post subject: Re: Bidirectional One-to-one association n+1 select problem
PostPosted: Sun Jun 27, 2010 7:24 am 
Newbie

Joined: Sun Jun 27, 2010 7:14 am
Posts: 1
jogundipe wrote:
The only improvement I seem to be able to make is to reduce the queries to two by adding fetch="join" to the <many-to-one> or doing a "join fetch" in a hql query or setting a FetchMode in a criteria query

Changing the many-to-one mapping to:
Code:
<many-to-one class="Person" name="Person" column="PersonID" not-null="true" unique="true" fetch="join"/>

results in:

Code:
SELECT customer0_.id as id0_1_, customer0_.Version as Version0_1_, customer0_.Number as Number3_0_1_, customer0_.PersonID as PersonID0_1_, person1_.id as id3_0_, person1_.Version as Version3_0_, person1_.FirstName as FirstName3_0_, person1_.LastName as LastName3_0_ FROM Tests.dbo.customer customer0_ inner join Tests.dbo.Person person1_ on customer0_.PersonID=person1_.id WHERE customer0_.id=@p0;@p0 = 4
SELECT customer0_.id as id0_1_, customer0_.Version as Version0_1_, customer0_.Number as Number3_0_1_, customer0_.PersonID as PersonID0_1_, person1_.id as id3_0_, person1_.Version as Version3_0_, person1_.FirstName as FirstName3_0_, person1_.LastName as LastName3_0_ FROM Tests.dbo.customer customer0_ inner join Tests.dbo.Person person1_ on customer0_.PersonID=person1_.id WHERE customer0_.PersonID=@p0;@p0 = 1
Before assert
After assert, about to access person property
After accessing person property


This time just two queries both of which fetch the customer and join the person also fetching the person's properties at the same time.

The two queries are both run eagerly so all the writes to console come after the queries.
The two queries are almost exactly alike. But why is the second redundant query required at all.



You get two queries because your one-to-one association is bidirectional. If you make it unidirectional you will have only one query. But I still can't answer why second query is made because all required information is fetched with first query. I'm still looking for answer too...


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