Hibernate Exception - Simultaneously Fetch Multiple Bags

>> Thursday, June 15, 2006

One of my customers has just upgraded to JBoss 4.0.4-GA, the process also required us to upgrade Hibernate products to the following versions:

  • Hibernate core – 3.2.0CR2
  • Hibernate Entity Manager – 3.2.0CR1
  • Hibernate annotations- 3.2.0CR1
We fixed some minor changes and improvements and then we bumped into the following exception

javax.persistence.PersistenceException: org.hibernate.HibernateException: cannot simultaneously fetch multiple bags
     at org.hibernate.ejb.Ejb3Configuration.createEntityManagerFactory(Ejb3Configuration.java:217)
     at org.hibernate.ejb.HibernatePersistence.createEntityManagerFactory(HibernatePersistence.java:114)
        ........
The Exception is thrown by org.hibernate.loader.BasicLoader and it means that when loading an entity Hibernate might has to simultaneously fetch two or more bags.



So what is the problem?

When an entity has more than one non-lazy association that might be interpreted as a bag (e.g., java.util.List or java.util.Collection properties annotated with @org.hibernate.annotations.CollectionOfElements or @OneToMany or @ManyToMany and not annotated with @org.hibernate.annotations.IndexColumn) hibernate will fail to fetch the entity correctly.
Lets say that we have a Parent class, and two children classes (Child1 and Child2), the parent class has a bag for each of the children types, as illustrated by the following mapping

@OneToMany(mappedBy="parent",cascade=CascadeType.ALL, fetch=FetchType.EAGER)
List<Child1> child1s = new LinkedList<Child1>();
@OneToMany(mappedBy="parent",cascade=CascadeType.ALL, fetch=FetchType.EAGER)
List<Child2> child2s= new LinkedList<Child2>();

We have to notice the bag semantic and the EAGER fetching on both associations, the cascading is not important to our problem. When parsing the mapping Hibernate pre-build the following (I changed the columns aliases for readability) SQL

select 
   parent0_.id as p_id, parent0_.name as p_name, child1s1_.parent_id as c1_p_id,
   child1s1_.id as c1_id, child1s1_.id as c1_id_1, child1s1_.parent_id as 
   c1_p_id_1, child1s1_.value as c1_val,child2s2_.parent_id as c2_p_id,
   child2s2_.id as c2_id, child2s2_.id as c2_id_, 
   child2s2_.parent_id as c2_p_id_1, child2s2_.value as c2_val 
from 
   PARENT parent0_ left outer join 
           CHILD1 child1s1_ on parent0_.id=child1s1_.parent_id left outer join 
           CHILD2 child2s2_ on parent0_.id=child2s2_.parent_id 
where 
   parent0_.id=?

Now assume the following data in our database


PARENT table

ID

NAME

122

PARENT-1




CHILD1 table

ID

VALUE

PARENT_ID

123

CHILD1-1

122

1

CHILD1-2

122



CHILD2 table

ID

VALUE

PARENT_ID

124

CHILD2-1

122



Replacing the question mark with 122 and execute the above select statement yields the following result


P_ID

P_NAME

C1_P_ID

C1_ID

C1_ID_1

C1_P_ID_1

C1_VAL

C2_P_ID

C2_ID

C2_ID_

C2_P_ID_1

C2_VAL

122

Parent-1

122

123

123

122

CHILD1-1

122

124

124

122

CHILD2-1

122

Parent-1

122

1

1

122

CHILD1-2

122

124

124

122

CHILD2-1



As we can see there is no why Hibernate can understand from the query result that the Child2 bag has only one element, it thinks it has two identical elements(!) - the red cells.
One more comment

  • In the JIRA it says that in the future Hibernate might fall back to sub-select strategy

Workarounds

I've found three strategies to overcome this issue, the first one is to use LAZY fetching when possible. I don't even have to change all of my associations to be lazy fetched, I just can't have more than one EGAR association on the same entity. The second solution is to replace bag semantics with List semantics this can be done by adding the @IndexColumn on the relation.

@OneToMany(mappedBy="parent",cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@IndexColumn(name="INDEX_COL")
List<Child1> child1s = new LinkedList<Child1>();

The usage of @IndexColumn helps solving the problem since now Hibernate has a List semantic for the association and when fetching the parent it also fetches the index of each element on the list. Using this index Hibernate can tell if the element on the list has already been loaded. We have to remember that by adding @IndexColumn on the inverse side of an association (as illustrated above) we have to maintain the index “by hand”, Hibernate doesn't maintain the index on the inverse side of the relation (I'll explain how we can instruct Hibernate to maintain such an index on one of the next entries in my OLD blog).

The third solution is to replace java.util.List and java.util.Collection with java.util.Set. We have to think about it - do we really need a List/Collection (a bag)? In many cases the only reason to use a List is that we are used to.

21 comments:

Anonymous July 13, 2010 at 6:59 AM  

Great article, I have been trying to find a solution for weeks but no answer so far
I am not sure this apply to my problem...

I have this type of relationship and i consider manyToOne and oneToOne relationships eagerly fetched and onetoMany lazily fetched.

I want to fetch Entity A and all its association where a.id=?

where...
1>* oneToMany
1>1 oneToOne

A 1>* B
B 1>1 C
C 1>* D
B 1>1 E
E 1>* E
B 1>1 F
F 1>* G

Is it possible to load this entity in a single query?

Eyal Lupu July 13, 2010 at 9:14 AM  

Hi,
Thank you for your feedback.
Did you try using fetch join? Take a look at this entry in my old blog.

Eyal Lupu

Anonymous August 19, 2010 at 2:06 PM  

Thanks for explaining this. Great example. Hope more people manage to find this.

I'll be going with solution #3: making my Lists into Sets. That is what they are after all.

dim5b October 21, 2010 at 2:22 AM  

Earlier in July I posted a problem regarding multiple bags and queries in your very helpful blog. However I have not yet found a solution to my problem

Due to time constraints I solved this problem using .size() for each lazy. collection (changed to Set) I wanted to fetch.

However I have not yet being able to find any other solution the would be more efficient. All the books out there keep to the basics and never go the extra mile!!

Any help would be greatly appreciated because I have been stuck on this for months!!

Anonymous October 27, 2010 at 6:34 PM  

Hey, I can't view your site properly within Opera, I actually hope you look into fixing this.

Wrocław December 4, 2010 at 6:09 AM  

Nice article.

Actually there is one more quite convenient strategy to fix this. You need to annotate you OneToMany relationship:

@Fetch(FetchMode.SUBSELECT)

This actually does not have a drawback except for poorer performance. I'll cover this soon on my emerging blog: http://beautiful-code.blogspot.com

Justin Cranford May 17, 2011 at 8:52 AM  

Workaround #1 did not work for me.


I've found three strategies to overcome this issue, the first one is to use LAZY fetching when possible. I don't even have to change all of my associations to be lazy fetched, I just can't have more than one EGAR association on the same entity.


I had to debug Hibernate 3.6.0 Final to find out why. The 1 bag role limit is applied recursively.

For example, my model has two entities with only one EAGER fetch each:

Parent -> Child
Grandparent -> Parent

Hibernate throws an exception at BasicLoader.java:94 on Grandparent because it has two bag roles.

93: if ( bagRoles != null && bagRoles.size() > 1 ) {
94: throw new MultipleBagFetchException( bagRoles );
95: }

Looking at List bagRoles, it has two entries causing MultipleBagFetchException to be thrown...

bagRoles:
[0]org.abc.Grandparent.parents
[1]org.abd.Parent.children

***

Workarounds #2 and #3 are Hibernate specific. Is there a way to do this with JPA-only annotations? I am using JPA 2.0 in JBoss 6.0 Final.

Thanks,
Justin

Anonymous May 19, 2011 at 7:06 AM  

Nice Article. gives thorough understanding of the problem

Unknown April 13, 2012 at 8:21 AM  
This comment has been removed by the author.
Unknown April 13, 2012 at 8:22 AM  

Hi,

I have tried to use LAZY loading for both entities. but I am getting following exception:

Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role:

Can you please help me to resolve this issue. As I am using OnetoMany relationship, my preference will be LAZY loading.

Regards
Shirish

Unknown April 13, 2012 at 8:24 AM  
This comment has been removed by the author.
Unknown April 13, 2012 at 8:25 AM  
This comment has been removed by the author.
Unknown April 13, 2012 at 8:25 AM  
This comment has been removed by the author.
Unknown April 13, 2012 at 8:26 AM  
This comment has been removed by the author.
Anonymous April 22, 2012 at 1:38 PM  

I use Tomcat and I have not this problem, it's interesting :)

Anonymous June 11, 2012 at 3:13 PM  

Best solution I came across was to ditch Hibernate due to it being so janky and use EclipseLink which is a world better in stability and support.

Seriously, they may have led the way when all this ORM stuff was just cooking but now, they really don't get the whole usability thing. Shame really.

Anonymous July 4, 2012 at 9:59 AM  

Hibernate is garbage, their support is horrible. We got rid of Hibernate which was the best thing we ever did. Saved us so much headache.

Hebert October 15, 2012 at 12:41 PM  

Nice article.

Anonymous August 17, 2013 at 2:05 AM  

Thank you for great explanation of the problem!

Алексей Кузнецов March 24, 2014 at 1:28 AM  

But why Hibernate can not notice that both results have the same 124 values? It indicates that there is only one row in the child table, or, perhaps, Hibernate is not smart enought?

Anand Vijapur April 26, 2014 at 3:01 AM  

As i understand, we need to consider the structure of the primary key used by Hibernate. From the perspective of Hibernate, "a bag does not have a primary key." If there is just the one child C1, hibernate determines number of C1 objects for a parent P, not by the number of records in the result set, but by looking up the parent id. However, if there is the second child C2 as in the example, hibernate cannot be sure how many records for C1 and how many for C2!

Please correct me if this is incorrect.

  © Blogger templates Sunset by Ourblogtemplates.com 2008

Back to TOP