Hibernate Exception - Simultaneously Fetch Multiple Bags
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:
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
Replacing the question mark with 122 and execute the above select statement yields the following result
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
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.
- Hibernate core – 3.2.0CR2
- Hibernate Entity Manager – 3.2.0CR1
- Hibernate annotations- 3.2.0CR1
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.
Comments
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?
Thank you for your feedback.
Did you try using fetch join? Take a look at this entry in my old blog.
Eyal Lupu
I'll be going with solution #3: making my Lists into Sets. That is what they are after all.
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!!
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
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
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
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.
Please correct me if this is incorrect.