-->
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.  [ 12 posts ] 
Author Message
 Post subject: [3.6.4] Fetching 1:n relations using just one query
PostPosted: Tue May 17, 2011 5:24 am 
Beginner
Beginner

Joined: Sun Apr 25, 2010 7:14 am
Posts: 30
Hi,

since the version 3.6.4 a bug in hibernate was fixed (which is a good thing) but the fix now breaks my application (which is bad) and i have no
clue how i am supposed to do what i would like to do :(

The fix which breaks my application is:
http://opensource.atlassian.com/project ... e/HHH-2049

My situation:
I have a 1:n relation... lets call it Bean with many ChildBeans. I would like to get a list with Beans and preload the associated ChildBeans with
ONE select clause. I attached a working (actually not working as i would like it) example code to my post.

My problem:
It seams not to be possible to do this using one select clause as the Beans are multiplied by the count of their ChildBeans. In my example code
i get bean1 and bean2 two times as both of them have two childbeans. Of course this is not what i want. In the past i applied a filter to the
ChildBeans which assured that each Bean has just one ChildBean (that was ok for the logic of the application). But this workaround doesn't
work anymore with the fix applied to 3.6.4 as it leaves the collection uninitialized and causes many additional select statements on access.

I already tried the DistinctRootEntityResultTransformer which actually does what i would like (one Bean with multiple ChildBeans) but the
implementation of the Transformer makes paging using setMaxResults impossible.

Preferred solution:
My preferred solution would actually be to add a filter to the ChildBeans again but initialize the collection with the result. Yes i know that
the state of the object does not reflect the state from the database correctly but this is the only way to keep things working. I think not
filtering the collection would break other things (as sorting or paging) too.

How i am supposed to solve my problem using Hibernate 3.6.4?

Please help me.

Yours
Thomas



My code:
Code:
CREATE TABLE IF NOT EXISTS `beans` (
  `id` int(11) NOT NULL,
  `value` varchar(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `beans` (`id`, `value`) VALUES
(1, 'bean1'),
(2, 'bean2');

CREATE TABLE IF NOT EXISTS `childbeans` (
  `id` int(11) NOT NULL,
  `fkbeans` int(11) NOT NULL,
  `value` varchar(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `childbeans` (`id`, `fkbeans`, `value`) VALUES
(1, 1, 'childbean1'),
(2, 1, 'childbean2'),
(3, 2, 'childbean3'),
(4, 2, 'childbean4');

Code:
package de.test;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;

@Entity
@Table(name = "beans")
public class Bean {
   @Id
   @Column(name = "id", nullable = false)
   private Integer id;

   @Column(name = "value", nullable = false, length = 20)
   private String value;

   @OneToMany(mappedBy = "bean")
   @Cascade(value = { CascadeType.ALL })
   private Set<ChildBean> childbeans;

   public Bean() {
      this.childbeans = new HashSet<ChildBean>();
   }

   public Integer getId() {
      return id;
   }

   public void setId(Integer id) {
      this.id = id;
   }

   public String getValue() {
      return value;
   }

   public Set<ChildBean> getChildbeans() {
      return childbeans;
   }

   public void setChildbeans(Set<ChildBean> childbeans) {
      this.childbeans = childbeans;
   }
}

Code:
package de.test;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table(name = "childbeans")
public class ChildBean {
   @Id
   @Column(name = "id", nullable = false)
   private Integer id;

   @ManyToOne(fetch = FetchType.LAZY)
   @JoinColumn(name = "fkbeans", updatable = false, nullable = false)
   private Bean bean;

   @Column(name = "value", nullable = false, length = 20)
   private String value;

   public Integer getId() {
      return id;
   }

   public void setId(Integer id) {
      this.id = id;
   }

   public Bean getBean() {
      return bean;
   }

   public void setBean(Bean bean) {
      this.bean = bean;
   }

   public String getValue() {
      return value;
   }

   public void setValue(String value) {
      this.value = value;
   }
}

Code:
package de.test;

import java.util.List;

import org.hibernate.FetchMode;
import org.hibernate.Session;

public class Main {
   public static void main(String[] args) throws Exception {
      Session session = Hibernate.getSessionFactory().getCurrentSession();
      session.beginTransaction();
      
      List<Bean> beans = (List<Bean>) session.createCriteria(Bean.class)
         .setFetchMode("childbeans", FetchMode.JOIN)
         .list();
      
      for (Bean bean : beans) {
         System.out.println(bean.getValue() + ": " + bean.getChildbeans().size());
      }
      
      session.getTransaction().commit();
   }
}


Output:
Code:
bean1: 2
bean1: 2
bean2: 2
bean2: 2


Top
 Profile  
 
 Post subject: Re: [3.6.4] Fetching 1:n relations using just one query
PostPosted: Tue May 17, 2011 9:23 am 
Beginner
Beginner

Joined: Sun Apr 25, 2010 7:14 am
Posts: 30
Hi again,

i found this FAQ entry that describes my problem:
http://community.jboss.org/wiki/Hiberna ... ct_keyword

I understand (and understood before the FAQ entry) why this happens but writing an FAQ entry doesn't make it less a bug. It is NOT expected behaviour to get duplicates
from list(). I know that it is not possible to use setMaxResults on a query like this and get the expected result. But this is the reason why i would like to filter the
collection down to one item and want the collection to be initialized with this single item. I know that the collection is not complete but this can be handled by the application
(actually the application just needs this single item in that case). What i don't want to have are many additional select statements (in the past i had 1... now i have 51).

So... is there any way to workaround this bugfix?

Thanks again.

Yours Thomas


Top
 Profile  
 
 Post subject: Re: [3.6.4] Fetching 1:n relations using just one query
PostPosted: Wed Jun 08, 2011 12:53 pm 
Newbie

Joined: Wed Jun 08, 2011 12:49 pm
Posts: 4
Hi Thomas,

This blog post describes a similar situation with a solution using 2 queries: http://floledermann.blogspot.com/2007/1 ... tinct.html


Top
 Profile  
 
 Post subject: Re: [3.6.4] Fetching 1:n relations using just one query
PostPosted: Mon Jun 20, 2011 12:02 pm 
Beginner
Beginner

Joined: Sun Apr 25, 2010 7:14 am
Posts: 30
Hi kpage,

thanks for this link. As it seems this could solve my problem (i didn't try yet but i think it could ;) ).

Yours
Thomas


Top
 Profile  
 
 Post subject: Re: [3.6.4] Fetching 1:n relations using just one query
PostPosted: Tue Jun 21, 2011 3:40 am 
Beginner
Beginner

Joined: Sun Apr 25, 2010 7:14 am
Posts: 30
Hi kpage,

i took a deeper look at the blog post and i think that this will not work under all circumstances. If you filter the collection
and sort by an collection property the solution will not work. The reason is that the order could be different from the
"get the ids"-criteria and the "fetching"-criteria (as the collection is not filtered). The only way to fix this is to sort the
result of the "fetching"-criteria manually according to the order from the "get the ids"-criteria.

Yours
Thomas


Top
 Profile  
 
 Post subject: Re: [3.6.4] Fetching 1:n relations using just one query
PostPosted: Thu Aug 04, 2011 1:06 pm 
Newbie

Joined: Wed Jun 08, 2011 12:49 pm
Posts: 4
Hi Thomas,

Sorry I didn't notice your reply until today. I also found a problem with the solution in that post. The author adds sort columns as projections to the "get identifiers" query, because Postgres does not support a query like:

select distinct id from ... order by birthDate

I'm not familiar with Postgres, but other databases like MS SQL Server and Oracle will complain that birthDate must be in the select clause when the "distinct" modifier is used. This works fine if there is one birthDate per id, i.e. birthDate and id are 1:1 in the result set. However, if joins introduce a multiple cardinality of some column to id, limiting this query via "setFirstResult" and "setMaxResults" will return an incomplete list of identifiers.

To avoid this problem, I modified the query as follows:

select id from ... group by id order by min(birthDate)

This assumes that you are sorting by birthDate in ascending order. If you are sorting in descending order, the min should be a max (although this only matters if your query has more than one birthDate per id).

I'm not quite sure what you are referring to in your last post. I'm assuming that both the identifier query and the entity query must have the same "order by" applied, does that solve your problem?


Top
 Profile  
 
 Post subject: Re: [3.6.4] Fetching 1:n relations using just one query
PostPosted: Tue Aug 23, 2011 9:10 am 
Beginner
Beginner

Joined: Sun Apr 25, 2010 7:14 am
Posts: 30
Hi kpage,

an i didn't notice your reply until today :(

No my problem is a bit different. For example you have the following data:
Code:
tab1
id value
1  value1
2  value2
3  value3

tab2
id reftab1 language value
1  1       1        value11
2  1       2        value12
3  2       1        value21
4  2       2        value22

As you can see the tab1 has a collection of tab2s.

Now you would like to have all tab1s including their tab2s of language 1 and sort by tab2.value.
You would have to filter the "get identifier"-query by tab1.tab2s.language = 1 and sort by tab1.tab2s.value. With the identifiers from this query
you could then read the complete tab1s. But as the tab1.tab2s are not filtered in the 2nd query the order achieved by "order by tab1.tab2s.value" might be
different from the 1st query. So you have to sort it "by hand" according to the result from the 1st query.

Actually this bugfix of hibernate brings quite a lot problems and dozens of unneeded queries and logic. The best solution for me would be a flag or
something like that to disable this fix.

Yours
Thomas


Top
 Profile  
 
 Post subject: Re: [3.6.4] Fetching 1:n relations using just one query
PostPosted: Tue Aug 23, 2011 10:52 am 
Newbie

Joined: Wed Jun 08, 2011 12:49 pm
Posts: 4
Hi Thomas,

Could you keep the "language" filter on both the "identifier" and "entities" queries? For example:

(I called the collection of tab2 objects "children". This example assumes you're using MySQL, other databases won't like you using Projections.id() with a different "order by").

Code:
      Criteria idCriteria = getCriteria(session);
      idCriteria.setFirstResult(1).setMaxResults(1);
      idCriteria.setProjection(Projections.id());
      List<Integer> identifiers = (List<Integer>)idCriteria.list();

      Criteria criteria = getCriteria(session);
      criteria.add(Restrictions.in("id", identifiers));
      List<Tab1> tabs = (List<Tab1>)criteria.list();

  private static Criteria getCriteria(Session session) {
    Criteria criteria = session.createCriteria(Tab1.class);
    Criteria subcriteria = criteria.createCriteria("children");
    subcriteria.addOrder(Order.asc("value"));
    subcriteria.add(Restrictions.eq("language", 1));
    return criteria;
  }


Top
 Profile  
 
 Post subject: Re: [3.6.4] Fetching 1:n relations using just one query
PostPosted: Wed Aug 24, 2011 2:18 am 
Beginner
Beginner

Joined: Sun Apr 25, 2010 7:14 am
Posts: 30
Hi kpage,

it would work in means of "produce the correct result" but due to the bugfix in hibernate 3.6.4 it would leave the collection "children" uninitialized. This would
lead to an extra query for every single element in the result on access on children.

Actually i want a list of items with the data for the currently active language. Before version 3.6.4 i could just filter the collection and get the correct result in
one query. With 3.6.4 (and later) i get an extra query for every element in the list (usually 20 items so the query count increases from 1 to 21 (2000% increase!!!!)).

The solution using 2 querys (id and entity query) would probably work but then i would have to sort the result of the entity query manually (as i cannot apply the order to
the entity query if the order-by-field is in the collection) and still get an increase of 100% in query count.

Yours
Thomas


Top
 Profile  
 
 Post subject: Re: [3.6.4] Fetching 1:n relations using just one query
PostPosted: Wed Aug 24, 2011 11:42 am 
Newbie

Joined: Wed Jun 08, 2011 12:49 pm
Posts: 4
Have you tried setting the FetchMode to JOIN or SUBSELECT? If that works, it should use 3 queries (identifier query, root entities query, and one query to retrieve the "tab2" collections for all the root entities).

I'm not quite sure what you mean by: "The solution using 2 querys (id and entity query) would probably work but then i would have to sort the result of the entity query manually (as i cannot apply the order to the entity query if the order-by-field is in the collection)". Do you mean that the subcollection is not sorted? Or does your database not support ordering by a column that's not being selected?

If you want to sort the subcollection, I think that you can specify the name of a comparator class to use for the set in your .hbm.xml. I'm not sure if there is a way to specify this per Criteria.

If your database does not support ordering by a column that's not being selected, you can use the "order by min(subcollectionColumn)" workaround.

To do that, use a "group by" projection on the "identifiers" query. You need to know the name of the identifier property:

Code:
criteria.setProjection(Projections.id());
criteria.setProjection(Projections.groupProperty("id"));


Then I use the NativeSQLOrder class to construct the min/max expressions. I found this class attached to this ticket:

http://opensource.atlassian.com/project ... e/HHH-2381

The "{" brackets are special syntax for NativeSQLOrder to indicate that the contained expression is a field that needs to be looked up, not raw sql.

Code:
NativeSQLOrder.asc("min({" + field + "})")

or

NativeSQLOrder.desc("max({" + field + "})")


Top
 Profile  
 
 Post subject: Re: [3.6.4] Fetching 1:n relations using just one query
PostPosted: Sat Aug 27, 2011 7:35 am 
Beginner
Beginner

Joined: Sun Apr 25, 2010 7:14 am
Posts: 30
Hi kpage,

thanks for your reply. Currently i am using FetchMode.JOIN which leads to the described behaviour. I'll do some tests with JOIN (maybe i am doing it wrong) and SUBSELECT to check if it would work.

Quote:
Do you mean that the subcollection is not sorted? Or does your database not support ordering by a column that's not being selected?

Neither ;)

Take the following (pseudo) SQL queries as an example:
Code:
SELECT id FROM tab1, tab2 WHERE tab1.id = tab2.reftab1 __AND tab2.language = 1__ ORDER BY tab2.value
SELECT tab1.*, tab2.* FROM tab1, tab2 WHERE tab2.id = tab2.reftab1 AND tab1.id IN (...) ORDER BY tab2.value

The order of the 2nd (entity) query might be different from the order of the 1st (id) query as the 1st query contains a restriction which the 2nd query doesn't.

If you have the following (combined) data:
Code:
tab1.id tab2.language tab2.value
1       1             ccc
1       2             aaa
2       1             bbb
2       2             ddd

The result of the 1st query would be: 2 1
The result of the 2nd query would be: 1 2 1 2 combined to 1 2
So you would have to sort the 2nd result according to the 1st result by hand.

Yours
Thomas

Edit:
As it seems FetchMode.SUBSELECT is not available for criteria queries. So i can not use it :(


Top
 Profile  
 
 Post subject: Re: [3.6.4] Fetching 1:n relations using just one query
PostPosted: Wed Aug 31, 2011 3:05 am 
Beginner
Beginner

Joined: Sun Apr 25, 2010 7:14 am
Posts: 30
Hi together,

in my application i managed to reduced the queries from 21 (one for the list and one for every list items collection) to 2 (one for the list and one for all item collections).
I did this by using the FetchMode.SUBSELECT in the bean (using annotations). This also works for criteria queries.

But this actually is just a workaround. I don't want the whole collections to be loaded as this means a lot of overhead (by loading the not needed languages as well). So
i am still looking for a solution that lets me load a partial collection and doesn't leave it uninitialized.

Yours
Thomas


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