web-dev-qa-db-fra.com

org.hibernate.loader.MultipleBagFetchException: impossible de récupérer simultanément plusieurs sacs

Voici mon code Ici, j'utilise plusieurs listes pour extraire des données de la base de données. Lors de la récupération des données de la requête hql, il affiche une 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
}

fichier 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>

Requête 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 ";

J'essaie la requête suivante pour récupérer les données de la base de données mais cela montre org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags Comment résoudre ce problème

12
xrcwrn

Comme expliqué dans cet article , Hibernate ne permet pas d'aller chercher plus d'un sac car cela générerait un produit cartésien .

Vous pouvez changer les sacs en ensembles et ajouter un order-by="id" attribut pour "simuler" un comportement de liste ordonnée:

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>

Mais ce n'est pas parce que vous le pouvez que vous le devriez.

Ce que vous pouvez faire est de récupérer au plus une collection dans la requête SQL d'origine, tandis que les autres collections sont récupérées ensuite à l'aide de requêtes secondaires. De cette façon, vous pouvez éviter le produit cartésien.

Une autre option consiste à utiliser extraction à plusieurs niveaux de l'enfant jusqu'aux entités parentes .

20
Vlad Mihalcea

Pour moi, j'ai eu la même erreur et j'ai résolu en ajoutant l'annotation d'hibernation @ Fetch

@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
@Fetch(value = FetchMode.SUBSELECT)
private List<Child> childs;
22

Changer pour Set est la meilleure solution. Cependant, si vous ne pouvez pas remplacer le List par Set (comme dans mon cas, il y avait une utilisation intensive des balises JSF spécifiques à Lists), et si vous pouvez utiliser Hibernate les annotations propriétaires, vous pouvez spécifier @IndexColumn (name = "INDEX_COL"). Cette solution fonctionnait mieux pour moi, passer à Set nécessiterait des tonnes de refactoring.

Donc, votre code ressemblerait à ceci:

@IndexColumn (name = "INDEX_COL")
private List<BillPaidDetails> billPaidDetailses = new ArrayList<BillPaidDetails>();

@IndexColumn (name = "INDEX_COL")
private List<BillProduct> billProductList = new ArrayList<BillProduct>();

Comme Igor l'a suggéré dans les commentaires, vous pouvez également créer des méthodes proxy pour renvoyer les listes. Je n'ai pas essayé cela, mais ce serait une bonne alternative si vous ne pouvez pas utiliser les annotations propriétaires d'Hibernate.

2
L. Holanda

Vous pouvez uniquement effectuer une jointure-extraction après une relation pour une entité (billPaidDetailses ou billProductList).

Pensez à utiliser des associations paresseuses et à charger des collections lorsque cela est nécessaire, OR à l'aide d'associations paresseuses et à charger manuellement des collections avec Hibernate.initialize(..). Du moins, c'est la conclusion à laquelle je suis arrivé quand j'ai eu a problème similaire .

Dans tous les cas, il faudra plus d'une requête à la base de données.

2
enlait

J'ai utilisé la nouvelle annotation @OrderColumn au lieu de @IndexColumn (obsolète, voir: https://docs.jboss.org/hibernate/orm/5.2/javadocs/org/hibernate/annotations/IndexColumn.html ) et ça marche maintenant.

Annotez l'une des collections avec @OrderColumn, par ex.

@ManyToMany(cascade = CascadeType.ALL)
@OrderColumn
private List<AddressEntity> addresses = Lists.newArrayList();

@Builder.Default
@ManyToMany(cascade = CascadeType.ALL)
private List<BankAccountEntity> bankAccounts = Lists.newArrayList();
0
hakson

Je trouve en utilisant @PostLoad méthode annotée dans l'entité la plus utile, je ferais quelque chose comme

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

de cette façon, je suis en mesure de contrôler avec précision le chargement et l'initialisation des collections dans la même transaction qui a chargé l'entité.

0
alibttb