org.hibernate.loader.MultipleBagFetchException: невозможно одновременно получить несколько пакетов

Ниже приведен мой код. Здесь я использую несколько списков для извлечения данных из базы данных. При извлечении данных из запроса hql отображается исключение.

Поджо Класс

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 Как решить эту проблему


person xrcwrn    schedule 10.07.2014    source источник
comment
Вы пытались изменить свои списки на наборы?   -  person JamesB    schedule 10.07.2014
comment
Эта статья может вам помочь: blog.eyallupu.com/2010/06/   -  person JamesB    schedule 10.07.2014
comment
Как называется свойство уникального идентификатора в BillProduct?   -  person JamesB    schedule 10.07.2014
comment
Insted of List Sets работают. Но я хочу сделать со списком   -  person xrcwrn    schedule 11.07.2014
comment
Вам действительно нужно использовать списки?   -  person JamesB    schedule 11.07.2014
comment
Если возможно со списком, я хочу его использовать.   -  person xrcwrn    schedule 11.07.2014
comment
Выбрал простой способ преобразовал весь список в набор   -  person xrcwrn    schedule 11.07.2014


Ответы (7)


Hibernate не позволяет получить более одного мешка, потому что это создаст декартово произведение.

Теперь вы найдете множество ответов, сообщений в блогах, видео или других ресурсов, говорящих вам использовать Set вместо List для ваших коллекций.

Это ужасный совет!

Использование Sets вместо Lists заставит MultipleBagFetchException исчезнуть, но декартово произведение останется.

Правильное решение

Вместо использования нескольких JOIN FETCH в одном запросе API JPQL или Criteria:

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();

Вы можете сделать следующий трюк:

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();

Пока вы получаете не более одной коллекции с помощью JOIN FETCH, все будет в порядке. Используя несколько запросов, вы избежите декартова произведения, поскольку любая другая коллекция, кроме первой, извлекается с использованием вторичного запроса.

person Vlad Mihalcea    schedule 10.07.2014
comment
Я думаю, что эта статья более актуальна для фактической загрузки Sets blog.eyallupu. com/2010/06/ Кроме того, замена пакетов на наборы не делает результат запроса соединения-выборки менее декартовым произведением, что само по себе может быть проблемой для больших коллекций. - person enlait; 22.05.2015
comment
максимум один можно приравнять не более чем к одному. Это означает, что только один, поскольку невозможно запросить таблицу 0,5. мой вопрос в том, почему здесь используется формулировка не более одного - person Yene Mulatu; 26.07.2020
comment
Влад, а какое решение, когда мы используем @EntityGraph в JpaRepository? - person Suraj Gautam; 15.09.2020
comment
Переключитесь с этого на собственный метод JpaRepository, который делает то, что я сказал вам в этом ответе. Это решение. - person Vlad Mihalcea; 16.09.2020

У меня была такая же ошибка, и я решил ее, добавив аннотацию hibernate @Fetch.

@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
@Fetch(value = FetchMode.SUBSELECT)
private List<Child> childs;
person BERGUIGA Mohamed Amine    schedule 19.07.2015
comment
Что мне делать, если я хочу использовать ленивый тип выборки - person GokulRaj K.N.; 26.09.2019

Изменение на 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.

person L. Holanda    schedule 15.10.2014
comment
Не обязательно. Вы можете добавить дополнительные методы для наборов и проксировать их в списки. - person Igor Donin; 20.05.2015

Вы можете выполнять соединение-выборку только после одного отношения для объекта (либо billPaidDetailses, либо billProductList).

Рассмотрите возможность использования ленивых ассоциаций и загрузки коллекций, когда они необходимы, ИЛИ использование ленивых ассоциаций и загрузка коллекций вручную с помощью Hibernate.initialize(..). По крайней мере, к такому выводу я пришел, когда столкнулся с похожей проблемой.

В любом случае потребуется более одного запроса к базе данных.

person enlait    schedule 10.07.2014
comment
Я не согласен. Есть несколько способов сделать это без нескольких запросов, как ответили другие люди, и во многих случаях использование нескольких запросов приводит к серьезным проблемам с производительностью. - person Igor Donin; 20.05.2015
comment
@IgorDonin, ты прав, это возможно. Но это не серебряная пуля с точки зрения производительности. Представьте, что мне нужно загрузить контейнер, скажем, четырьмя наборами по 100 элементов в каждом. Не так много, верно? Запрос результата соединения-выборки будет 100000000 - person enlait; 22.05.2015
comment
Конечно, ты прав. Я видел случаи, когда было сгенерировано более 750 000 записей для заполнения 600 объектов. еще можно ;) - person Igor Donin; 23.05.2015

Я считаю, что использование аннотированного метода @PostLoad в объекте наиболее полезно, я бы сделал что-то вроде

@PostLoad
public void loadCollections(){
     int s1 = productReplacements.size();
     int s2 = billProductList.size();
}

таким образом я могу точно контролировать активную загрузку и инициализацию коллекций в той же транзакции, которая загрузила объект.

person alibttb    schedule 28.08.2019

Я использовал новую аннотацию @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();
person hakson    schedule 25.06.2019

Ваш запрос извлекает слишком много данных, и HIbernate не может загрузить их все. Сократите запрос и/или настройте свои объекты для получения только необходимых данных.

person Pracede    schedule 10.07.2014