Org.hibernate.loader.MultipleBagFetchException: не может одновременно извлекать несколько пакетов
Ниже приведен мой код. Здесь я использую несколько списков для извлечения данных из базы данных.
При извлечении данных из запроса hql он показывает исключение.
Класс Pojo
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 файл
<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
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 ";
Я пытаюсь выполнить запрос для извлечения данных из базы данных, но это показывает
org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags
Как разрешить этот
Ответы
Ответ 1
Как объясняется в этой статье, Hibernate не позволяет получать более одной сумки, потому что это может привести к декартовому продукту.
Вы можете изменить пакеты на наборы и добавить атрибут order-by="id"
для имитации поведения упорядоченного списка:
private Set<BillPaidDetails> billPaidDetailses = new LinkedHashSet<>();
private Set<BillProduct> billProductList = new LinkedHashSet<>();
<set name="billProductList" table="bill_product"
inverse="true" lazy="false" fetch="join" order-by="id">
<key>
<column name="bill_no" not-null="true" />
</key>
<one-to-many class="iland.hbm.BillProduct" />
</set>
<set name="billPaidDetailses" table="bill_paid_details"
inverse="true" lazy="false" fetch="select" order-by="id">
<key>
<column name="bill_no" not-null="true" />
</key>
<one-to-many class="iland.hbm.BillPaidDetails" />
</set>
Но только потому, что вы можете, это не значит, что вы должны.
Что вы можете сделать, это выбрать не более одной коллекции в исходном запросе SQL, в то время как другие коллекции извлекаются впоследствии с использованием вторичных запросов. Таким образом, вы можете избежать декартово произведение.
Другой вариант - использовать многоуровневую выборку от дочерних до родительских объектов.
Ответ 2
Для меня у меня была такая же ошибка, и я решил, добавив аннотацию спящего режима
@Fetch
@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
@Fetch(value = FetchMode.SUBSELECT)
private List<Child> childs;
Ответ 3
Вы можете только join-fetch следовать за одним отношением для сущности (либо billPaidDetailses
, либо billProductList
).
Рассмотрите возможность использования ленивых ассоциаций и загрузки коллекций, когда они нужны, ИЛИ используя ленивые ассоциации и загружая коллекции вручную с помощью Hibernate.initialize(..)
. По крайней мере, это был тот вывод, к которому я пришел, когда у меня была аналогичная проблема.
В любом случае это потребует нескольких запросов к базе данных.
Ответ 4
Лучшим решением является переход на Set
. Однако, если вы не можете заменить List
на Set
(как в моем случае, было тяжелое использование тегов JSF, характерных для Lists
), и если вы можете использовать аннотации Hibernate, вы можете указать @IndexColumn (name = "INDEX_COL")
. Это решение работало лучше для меня, переход на Set
потребовал бы тонны рефакторинга.
Итак, ваш код будет примерно таким:
@IndexColumn (name = "INDEX_COL")
private List<BillPaidDetails> billPaidDetailses = new ArrayList<BillPaidDetails>();
@IndexColumn (name = "INDEX_COL")
private List<BillProduct> billProductList = new ArrayList<BillProduct>();
Как предложил Игорь в комментариях, вы также можете создать прокси-методы для возврата списков. Я не пробовал это, но был бы хорошей альтернативой, если вы не можете использовать аннотации Hibernate.
Ответ 5
Я использовал новую аннотацию @OrderColumn вместо @IndexColumn (устарело, см.: https://docs.jboss.org/hibernate/orm/5.2/javadocs/org/hibernate/annotations/IndexColumn.html), и теперь она работает.
Аннотируйте одну из коллекций с помощью @OrderColumn, например
@ManyToMany(cascade = CascadeType.ALL)
@OrderColumn
private List<AddressEntity> addresses = Lists.newArrayList();
@Builder.Default
@ManyToMany(cascade = CascadeType.ALL)
private List<BankAccountEntity> bankAccounts = Lists.newArrayList();
Ответ 6
Я считаю, что использование аннотированного метода @PostLoad
в сущности наиболее полезно, я бы сделал что-то вроде
@PostLoad
public void loadCollections(){
int s1 = productReplacements.size();
int s2 = billProductList.size();
}
таким образом я могу точно контролировать загрузку и инициализацию коллекций в той же транзакции, в которой была загружена сущность.
Ответ 7
В запросе вы получите слишком много данных, и HIbernate не может загрузить их все.
Уменьшите свой запрос и/или настройте свои сущности для получения только необходимых данных.