org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags - java

Following is my code Here I am using multiple lists to fetch data from database.
On fetching data from hql query it is showing exception.
Pojo Class
public class BillDetails implements java.io.Serializable {
private Long billNo;
// other fields
#LazyCollection(LazyCollectionOption.FALSE)
private List<BillPaidDetails> billPaidDetailses = new ArrayList<BillPaidDetails>();
private Set productReplacements = new HashSet(0);
#LazyCollection(LazyCollectionOption.FALSE)
private List<BillProduct> billProductList = new ArrayList<BillProduct>();
//getter and setter
}
hmb.xml file
<class name="iland.hbm.BillDetails" table="bill_details" catalog="retail_shop">
<id name="billNo" type="java.lang.Long">
<column name="bill_no" />
<generator class="identity" />
</id>
<bag name="billProductList" table="bill_product" inverse="true" lazy="false" fetch="join">
<key>
<column name="bill_no" not-null="true" />
</key>
<one-to-many class="iland.hbm.BillProduct" />
</bag>
<bag name="billPaidDetailses" table="bill_paid_details" inverse="true" lazy="false" fetch="select">
<key>
<column name="bill_no" not-null="true" />
</key>
<one-to-many class="iland.hbm.BillPaidDetails" />
</bag>
<set name="productReplacements" table="product_replacement" inverse="true" lazy="false" fetch="join">
<key>
<column name="bill_no" not-null="true" />
</key>
<one-to-many class="iland.hbm.ProductReplacement" />
</set>
</class>
Hql query
String hql = "select distinct bd,sum(bpds.amount) from BillDetails as bd "
+ "left join fetch bd.customerDetails as cd "
+ "left join fetch bd.billProductList as bpd "
+ "left join fetch bpd.product as pd "
+"left join fetch bd.billPaidDetailses as bpds "
+ "where bd.billNo=:id "
+ "and bd.client.id=:cid ";
I am trying following query to fetch data from database but this is showing
org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags
How to resolve this

Hibernate doesn't allow fetching more than one bag because that would generate a Cartesian product.
Now, you will find lots of answers, blog posts, videos, or other resources telling you to use a Set instead of a List for your collections.
That's terrible advice!
Using Sets instead of Lists will make the MultipleBagFetchException go away, but the Cartesian Product will still be there.
The right fix
Instead of using multiple JOIN FETCH in a single JPQL or Criteria API query:
List<Post> posts = entityManager.createQuery("""
select p
from Post p
left join fetch p.comments
left join fetch p.tags
where p.id between :minId and :maxId
""", Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.getResultList();
You can do the following trick:
List<Post> posts = entityManager.createQuery("""
select distinct p
from Post p
left join fetch p.comments
where p.id between :minId and :maxId
""", Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();
posts = entityManager.createQuery("""
select distinct p
from Post p
left join fetch p.tags t
where p in :posts
""", Post.class)
.setParameter("posts", posts)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();
As long as you fetch at most one collection using JOIN FETCH, you will be fine. By using multiple queries, you will avoid the Cartesian Product since any other collection but the first one is fetched using a secondary query.

For me I had the same error and I solved by adding the annotation of hibernate
#Fetch
#OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
#Fetch(value = FetchMode.SUBSELECT)
private List<Child> childs;

Changing to Set is the best solution. However, if you cannot not replace the List with Set (like in my case, there was a heavy use of JSF tags specific to Lists), and if you can use Hibernate proprietary annotations, you can specify #IndexColumn (name = "INDEX_COL"). That solution worked better for me, changing to Set would require tons of refactoring.
So, your code would be something like this:
#IndexColumn (name = "INDEX_COL")
private List<BillPaidDetails> billPaidDetailses = new ArrayList<BillPaidDetails>();
#IndexColumn (name = "INDEX_COL")
private List<BillProduct> billProductList = new ArrayList<BillProduct>();
As Igor suggested in the comments, you could also create proxy methods to return the lists. I haven't tried that, but would be a good alternative if you cannot use Hibernate proprietary annotations.

You can only join-fetch following one relation for an entity (either billPaidDetailses or billProductList).
Consider using lazy associations and loading collections when they are needed, OR using lazy associations and loading collections manually with Hibernate.initialize(..). At least that was the conclusion I came to when I had a similar issue.
Either way it will take more than one query to the database.

I find using #PostLoad annotated method in the entity most useful, I'd do something like
#PostLoad
public void loadCollections(){
int s1 = productReplacements.size();
int s2 = billProductList.size();
}
this way I'm able to fine control the eager loading and initialization of collections in the same transaction that loaded the entity.

I used the new annotation #OrderColumn instead of #IndexColumn (deprecated see: https://docs.jboss.org/hibernate/orm/5.2/javadocs/org/hibernate/annotations/IndexColumn.html) and it works now.
Annotate one of the collections with #OrderColumn
e.g.
#ManyToMany(cascade = CascadeType.ALL)
#OrderColumn
private List<AddressEntity> addresses = Lists.newArrayList();
#Builder.Default
#ManyToMany(cascade = CascadeType.ALL)
private List<BankAccountEntity> bankAccounts = Lists.newArrayList();

Your request fetch too many data and HIbernate cannot load them all.
Reduce your request and/or configure your entities to retrieve just needed data

Related

Hibernate - n+1 select queries with 1-to-1

Organization is mapped to Address as 1-to-1:
Organization:
<one-to-one class="Address" constrained="true" name="address" property-ref="organizationId"/>
Address:
<many-to-one class="Organization" name="organization">
<column name="OrganizationID" not-null="false" unique="true"/>
</many-to-one>
this query generates addtitional select for every Organization + 1:
query = session.createQuery("select o from Organization as o where o.isCool=0").setReadOnly(true);
organizations = query.list();
http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/performance.html tells to fetch="join" but this doesn't make any difference. How to solve this problem? Any help is appreciated.
EDIT
In debugger i can see that address is actually not lazy loaded, i have no idea why.
Since you are using an HQL to fetch your stuff, it would not help to simply use the annotation or the attribute that you are trying, to avoid the n+1 problem.
The right solution would be to make use of 'FETCH JOIN' clause in your query. You can follow the following link for more details:
http://www.realsolve.co.uk/site/tech/hib-tip-pitfall.php?name=n1selects

Does cascade="all-delete-orphan" have any meaning in a Hibernate unidirectional many-to-many association with a join table?

I have two objects which form a parent-child relationship which have a many-to-many relationship. Following the recommendations in the Hibernate reference manual, I have mapped this using a join table:
<class name="Conference" table="conferences">
...
<set name="speakers" table="conference_speakers" cascade="all">
<key column="conference_id"/>
<many-to-many class="Speaker" column="speaker_id"/>
</set>
</class>
<class name="Speaker" table="speakers">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="firstName"/>
<property name="lastName"/>
</class>
My wish is that a single Speaker can be associated with many different Conferences, but also that any Speaker which is no longer referenced by any Conference is removed from the speakers table (as a Speaker with no associated conferences doesn't have much meaning in my project).
However, I've found that if I use cascade="all-delete-orphan", then if a Speaker which is associated with multiple Conferences is removed from just one of them, Hibernate attempts to delete the Speaker instance itself.
Below is a unit test which shows this behavior:
#Test
public void testRemoveSharedSpeaker() {
int initialCount = countRowsInTable("speakers");
Conference c1 = new Conference("c1");
Conference c2 = new Conference("c2");
Speaker s = new Speaker("John", "Doe");
c1.getSpeakers().add(s);
c2.getSpeakers().add(s);
conferenceDao.saveOrUpdate(c1);
conferenceDao.saveOrUpdate(c2);
flushHibernate();
assertEquals(initialCount + 1, countRowsInTable("speakers"));
assertEquals(2, countRowsInTable("conference_speakers"));
// the remove:
c1 = conferenceDao.get(c1.getId());
c1.getSpeakers().remove(s);
flushHibernate();
assertEquals("count should stay the same", initialCount + 1, countRowsInTable("speakers"));
assertEquals(1, countRowsInTable("conference_speakers"));
c1 = conferenceDao.get(c1.getId());
c2 = conferenceDao.get(c2.getId());
assertEquals(0, c1.getSpeakers().size());
assertEquals(1, c2.getSpeakers().size());
}
An error is thrown when s's removal from c1.speakers is processed, because Hibernate is deleting both the row in the join table and the speakers table row as well:
DEBUG org.hibernate.SQL - delete from conference_speakers where conference_id=? and speaker_id=?
DEBUG org.hibernate.SQL - delete from speakers where id=?
If I change cascade="all-delete-orphan" to just cascade="all", then this test works as expected, although it leads to the undesired behavior where I will end up with orphaned rows in my speakers table.
This leads me to wonder - is it even possible for Hibernate to know when to delete orphaned objects from the child-side of the relationship, but only when the child is not referenced by any other parents (whether or not those parents are in the current Session)? Perhaps I am misusing cascade="all-delete-orphan"?
I get the same exact behavior if I use JPA annotations instead of XML mapping such as:
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "conference_speakers",
joinColumns = #JoinColumn(name = "conference_id"),
inverseJoinColumns = #JoinColumn(name = "speaker_id"))
#org.hibernate.annotations.Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private Set<Speaker> speakers = new HashSet<Speaker>();
This is with Hibernate 3.6.7.Final, by the way.
DELETE_ORPHAN cascade mode is not defined for many-to-many relationship - only for one-to-many (the latter sports a "orphanRemoval=true|false" attribute within JPA standard #OneToMany annotation, so you don't have to resort to proprietary Hibernate annotation).
The reason for this is exactly as you've described - there's no way for Hibernate to figure out whether "orphaned" end of the many-to-many relationship is truly orphaned without running a query against the database which is both counter-intuitive and can (potentially) have serious performance implications.
Hibernate behavior you've described is therefore correct (well, "as documented"); though in a perfect world it would have alerted you to the fact that DELETE_ORPHAN is illegal on many-to-many during 2nd pass mappings compilation.
I can't think of a good way of achieving what you want to do, to be honest. The easiest (but database-specific) way would likely be to define a trigger on deletion from conference_speakers that would check whether this speaker is "truly" orphaned and delete it from speakers if so. The database-independent option is to do the same thing manually in DAO or listener.
Update: Here's an excerpt from Hibernate docs (Chapter 11.11, right after gray Note on CascadeType.ALL), highlights are mine:
A special cascade style, delete-orphan, applies only to one-to-many
associations, and indicates that the delete() operation should be
applied to any child object that is removed from the association.
Further down:
It does not usually make sense to enable cascade on a many-to-one or
many-to-many association. In fact the #ManyToOne and #ManyToMany don't
even offer a orphanRemoval attribute. Cascading is often useful for
one-to-one and one-to-many associations.

Why is hibernate deleting rows from join table when adding element to set mapping many-to-many?

Suposse I have two classes:
class A {
Set<B> bs
}
class B {
}
This mapping:
<set name="bs" table="bs_tab" cascade = "save-update">
<key column="a_id />
<many-to-many column="b_id" class="B"/>
</set>
And join table like this:
bs_tab(
a_id, b_id, primary key(a_id, b_id)
)
When I add some element to bs set and then call Session.saveOrUpdate(A instance) hibernate is deleting all rows in bs_tab coresponding to B instances that were in the set before adding new element.
How can I solve this?
Make sure to implement equals/hashCode correctly. I have the same kind of mapping (unidirectional many-to-many) and adding elements does not generate DELETE then INSERT SQL statements for the join table.

Hibernate many-to-many collection filtering

I have the following POJO with a Set inside:
class Word {
private Long id;
private String word;
private int type = WordListFactory.TYPE_DEFAULT;
private Set<Word> refs = new HashSet<Word>();
...
}
Here's the mapping XML:
<class name="kw.word.Word" table="word">
<id name="id" column="id" unsaved-value="null">
<generator class="native"/>
</id>
<property name="word"
unique="true"
not-null="true"/>
<property name="type"/>
<set name="refs"
table="word_key"
cascade="save-update">
<key column="word_id"/>
<many-to-many class="kw.word.Word"
column="word_ref_id"
fetch="join">
</many-to-many>
</set>
</class>
There are two tables: word and word_key. The latter links word-parents to word-children.
I'm trying to implement set items filtering when the data is fetched from DB. The resulting object set must contain only items with a specific type.
I tried various things:
Using filtering in mapping like (sorry for lack of brackets)
many-to-many class="kw.word.Word"
column="word_ref_id"
fetch="join">
filter name="word_type" condition="type=:type"
many-to-many
In the code that fetches data I enabled the filter and set the parameter. According to logs hibernate seems to ignore this particular filter as it there's no condition in resulting SQL query.
Using additional condition in Criteria
Word result = null;
session.beginTransaction();
Criteria crit = session.createCriteria(Word.class);
crit.add(Restrictions.like("word", key))
.createAlias("refs", "r")
.add(Restrictions.eq("r.type", getType()));//added alias and restriction for type
List list = crit.list();
if(!list.isEmpty())
result = list.get(0);
session.getTransaction().commit();
now the resulting SQL seems to be OK
select
this_.id as id0_1_,
this_.word as word0_1_,
this_.type as type0_1_,
refs3_.word_id as word1_,
r1_.id as word2_,
r1_.id as id0_0_,
r1_.word as word0_0_,
r1_.type as type0_0_
from
word this_
inner join
word_key refs3_
on this_.id=refs3_.word_id
inner join
word r1_
on refs3_.word_ref_id=r1_.id
where
this_.word like ?
and r1_.type=?
but right after this query there's another one that fetches all the items
select
refs0_.word_id as word1_1_,
refs0_.word_ref_id as word2_1_,
word1_.id as id0_0_,
word1_.word as word0_0_,
word1_.type as type0_0_
from
word_key refs0_
left outer join
word word1_
on refs0_.word_ref_id=word1_.id
where
refs0_.word_id=?
Maybe I'm doing something wrong?
From your given code snippet few points:
In case of many-to-many relationship you require 3 table , two entity tables and one join table. But as you are having same entity -Word , i think the given table structure and mappings seems fine.
Try to use HQL and specify 'LEFT JOIN FETCH' to specify which associations you need to be retrieved in the initial sql SELECT.
See this link related to many-to-many relationship,but they used criteria query.
Querying ManyToMany relationship with Hibernate Criteria

How to map many-to-many List in Hibernate with a Link Table

I would like to map a many-to-many in Hibernate using a link table. I have two classes, Parent and Child class, for example:
public class Parent{
private List<Child> _children;
//...getters and setters
}
I use a link table (link_table) with three columns link_id, parent_id, and child_id. The database is SQL server and id types are uniqueidentifier. So, I usually use guid for the id fields.
How can you implement this using the <list /> tag if this is the correct tag to use? Do you know of any good documentation to accomplish this?
I am currently getting a ConstraintViolationException but have not been able to find any good documentation or examples of this.
I think a main issue is: how to specify the link_id to be automatically generated in the link table.
I do this using annotations, specifically #ManyToMany and #JoinTable:
Hibernate Docs:
#Entity
public class Employer implements Serializable {
#ManyToMany(
targetEntity=org.hibernate.test.metadata.manytomany.Employee.class,
cascade={CascadeType.PERSIST, CascadeType.MERGE}
)
#JoinTable(
name="EMPLOYER_EMPLOYEE",
joinColumns=#JoinColumn(name="EMPER_ID"),
inverseJoinColumns=#JoinColumn(name="EMPEE_ID")
)
public Collection getEmployees() {
return employees;
}
}
#Entity
public class Employee implements Serializable {
#ManyToMany(
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
mappedBy = "employees",
targetEntity = Employer.class
)
public Collection getEmployers() {
return employers;
}
}
I don't think that it is possible (or necessary) to add a link_id primary key to the join table. The join table will usually consist of the primary keys of the two participating tables.
Using XML you will need syntax like this:
<class name="Parent">
....
<list name="children" table="link_table">
<key column="parent_id"/>
<many-to-many column="child_id"
class="Children"/>
</list>
...
</class>
<class name="Child">
...
<list name="parents" inverse="true" table="link_table">
<key column="child_id"/>
<many-to-many column="parent_id"
class="Parent"/>
</list>
...
</class>
Although I find annotations better to use.
I am not sure that you can pull this off easily for an existing database with existing data. Hibernate is usually better off defining its own data schema the first time you connect...
I've only pulled off many-to-many with annotations, but I think the hibernate documentation offers XML based examples: link text
I found a very good blog online which gives 2 ways to add additional fields to the many to many mapped hibernate column.
Traditionally we expect the many to many mapping to give a new table will FK's of tables mapped. But there are ways to tweak that and add more fields/column to this joined table.
This joined table may contain a PK or may contain some extra fields without PK.
See this blog for exact implementation See Here
And as per your example, you need an extra PK in the table so you declare a new table ParentChildren and declare your primary key as linkId. I am showing just the annoated parentchildren class, as annotations for many to many mapping in parent and children class can be referenced from post above.
#Entity
#Table(name = "parent_children")
public class ParentChildren{
#Id #GeneratedValue
private long linkId;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "parent_id")
private Parent parent;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "children_id)
private Children children;
// additional fields if you want
private boolean activated;
//getters and setters
}
}
So this will create a mapping table which has linkId as primary key and parent_id and children_id as Foreign Key. Just be clear on why you want a link_id separately as primary key and how you are going to use it.

Categories

Resources