Hazırda Beklet çokluBagFetchException atıyor - aynı anda birden çok torba getirilemiyor


471

Hazırda Beklet, SessionFactory oluşturma sırasında bu özel durumu atar:

org.hibernate.loader.MultipleBagFetchException: aynı anda birden çok torba getirilemiyor

Bu benim test durumum:

Parent.java

@Entity
public Parent {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 // @IndexColumn(name="INDEX_COL") if I had this the problem solve but I retrieve more children than I have, one child is null.
 private List<Child> children;

}

Child.java

@Entity
public Child {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @ManyToOne
 private Parent parent;

}

Bu soruna ne dersin? Ne yapabilirim?


DÜZENLE

Tamam, benim sorun başka bir "üst" varlık ebeveyn içinde olmasıdır, benim gerçek davranış şudur:

Parent.java

@Entity
public Parent {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @ManyToOne
 private AnotherParent anotherParent;

 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 private List<Child> children;

}

AnotherParent.java

@Entity
public AnotherParent {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 private List<AnotherChild> anotherChildren;

}

Hibernate, iki koleksiyondan hoşlanmıyor FetchType.EAGER, ancak bu bir hata gibi görünüyor, olağandışı şeyler yapmıyorum ...

Çıkarma FetchType.EAGERgelen Parentveya AnotherParentproblem çözer, ama gerçek bir çözüm kullanmaktır yüzden, ihtiyaç @LazyCollection(LazyCollectionOption.FALSE)yerine FetchType(sayesinde Bozho çözümü için).


Ben sormak istiyorum, hangi SQL sorgusu aynı anda alacak iki SQL koleksiyonu oluşturmak umuyorsunuz? Bunları başarabilecek SQL türleri, kartezyen birleştirme (potansiyel olarak yüksek verimsiz) veya ayrık sütunlar birliği (ayrıca çirkin) gerektirir. Muhtemelen bunu SQL'de temiz ve verimli bir şekilde başaramamak API tasarımını etkiledi.
Thomas W

@ThomasW Bunlar oluşturması gereken sql sorguları:select * from master; select * from child1 where master_id = :master_id; select * from child2 where master_id = :master_id
nurettin

1
Birden fazla varsa simillar hatayı alabilirsiniz List<child>ile fetchTypetanımlanmış birden fazla List<clield>
Büyük Zed

Yanıtlar:


555

Ben hazırda bekleme (JPA 2.0 destekleyen) yeni bir sürümü bu işlemek gerektiğini düşünüyorum. Ancak, aksi takdirde toplama alanlarına açıklama ekleyerek bu sorunu çözebilirsiniz:

@LazyCollection(LazyCollectionOption.FALSE)

fetchTypeÖzelliği @*ToManyek açıklamadan kaldırmayı unutmayın .

Ancak, çoğu durumda a'nın Set<Child>daha uygun olduğunu unutmayın List<Child>, bu yüzden gerçekten bir List- goSet

Ama setleri kullanarak bunu hatırlatmak olmaz underlaying ortadan kaldırmak Kartezyen Ürün tarafından açıklandığı gibi onun cevabını Vlad Mihalcea !


4
garip, benim için çalıştı. Kaldırmak mı fetchTypegelen @*ToMany?
Bozho

101
sorun JPA ek açıklamalarının 2'den fazla hevesle yüklenmiş koleksiyona izin vermeyecek şekilde ayrıştırılmasıdır. Ancak, kış uykusuna özgü açıklamalar buna izin verir.
Bozho

14
1'den fazla EAGER ihtiyacı tamamen gerçekçi görünüyor. Bu sınırlama sadece bir JPA denetimi mi? Birden fazla EAGER alırken aramam gereken endişeler nelerdir?
AR3Y35

6
Mesele şu ki, hazırda bekletme iki koleksiyonu tek bir sorgu ile getiremiyor. Bu nedenle, üst varlığı sorguladığınızda, sonuç başına 2 ekstra sorgu gerekir; bu normalde istemediğiniz bir şeydir.
Bozho

7
Bunun neden sorunu çözdüğüne dair bir açıklama yapmak harika olurdu.
Webnet

290

Sadece Listtürden türe değişir Set.

Ama bunu hatırlatmak olmaz underlaying ortadan kaldırmak Kartezyen Ürün tarafından açıklandığı gibi onun cevabını Vlad Mihalcea !


42
Liste ve Küme aynı şey değildir: bir küme düzeni korumaz
Matteo

17
LinkedHashSet siparişi koruyor
egallardo

15
Bu önemli bir ayrımdır ve düşündüğünüzde tamamen doğrudur. DB'de yabancı bir anahtar tarafından uygulanan tipik birebir, gerçekten bir Liste değildir, çünkü bir düzen korunmaz. Yani Set gerçekten daha uygun. Sanırım bu kış uykusunda fark yaratıyor, ama nedenini bilmiyorum.
fool4jesus

3
Ben aynı anda birden fazla çanta getiremiyorum ama ek açıklamaları nedeniyle değil vardı. Benim durumumda, ikisi ile sol birleşim ve ayrışmalar yapıyordum *ToMany. Türü değiştirmek de Setsorunumu çözdü. Mükemmel ve temiz çözüm. Bu resmi cevap olmalı.
L. Holanda

20
Cevabı beğendim, ancak milyon dolarlık soru şu: Neden? Set ile neden istisnalar gösterilmiyor? Teşekkürler
Hinotori

140

Kodunuza Hazırda Bekleme özel @Fetch ek açıklaması ekleyin:

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

Bu, Hazırda Bekletme hatası HHH-1718 ile ilgili sorunu çözmelidir


5
@DaveRlz neden subSelect bu sorunu çözüyor? Çözümünüzü ve onun çalışmasını denedim, ama bunu kullanarak sorunun nasıl çözüldüğünü bilmiyorum?
HakunaMatata

SetGerçekten mantıklı olmadıkça bu en iyi cevaptır . Sorgularda sonuç OneToManykullanarak tek bir ilişki olması Set; 1+<# relationships>sorgularda FetchMode.SUBSELECTsonuç kullanılması 1+1. Ayrıca, ek açıklamayı kabul edilen yanıtta ( LazyCollectionOption.FALSE) kullanmak daha da fazla sorgunun yürütülmesine neden olur.
mstrthealias

1
FetchType.EAGER bunun için uygun bir çözüm değildir. Hazırda Bekletme Profilleri ile devam etmek ve çözmek gerekiyor
Milinda Bandara

2
Diğer iki cevap, sorunumu çözmedi. Bunu yaptı. Teşekkür ederim!
Blindworks

3
SUBSELECT bunu neden düzelttiğini bilen var mı, ancak JOIN bunu düzeltmiyor mu?
Innokenty

42

Bu soru hem StackOverflow hem de Hibernate forumunda tekrar eden bir tema oldu, bu yüzden cevabı da bir makaleye dönüştürmeye karar verdim .

Aşağıdaki varlıklara sahip olduğumuzu düşünürsek:

resim açıklamasını buraya girin

Ve, Posttüm commentsve tagskoleksiyonlarla birlikte bazı üst varlıkları almak istersiniz .

Birden fazla JOIN FETCHdirektif kullanıyorsanız:

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

Hibernate rezilleri atacak:

org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags [
  com.vladmihalcea.book.hpjp.hibernate.fetching.Post.comments,
  com.vladmihalcea.book.hpjp.hibernate.fetching.Post.tags
]

Hazırda Beklet, birden fazla torba getirilmesine izin vermez, çünkü bu Kartezyen bir ürün oluşturur .

En kötü "çözüm"

Şimdi, size bir yanıt vermenizi söyleyen birçok cevap, blog yayını, video veya başka kaynak bulacaksınız. SetList koleksiyonlarınız için yerine .

Bu korkunç bir tavsiye. Yapma bunu!

Kullanma SetsyerineLists yapacak MultipleBagFetchExceptiongitmesini, ancak bu "düzeltmeyi" uygulanan bir süre sonra performans sorunu öğrenmek edeceğiz olarak Kartezyen Ürün hala aslında daha da kötü olan olacaktır.

Uygun çözüm

Aşağıdaki numarayı yapabilirsiniz:

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

İlk JPQL sorgusunda distinctSQL deyimine GELMEZ . Bu yüzden PASS_DISTINCT_THROUGHJPA sorgu ipucunu olarak ayarladık false.

DISTINCT'in JPQL'de iki anlamı vardır ve burada, getResultListSQL tarafında değil, Java tarafında döndürülen Java nesne referanslarını tekilleştirmemiz gerekir . Daha fazla ayrıntı için bu makaleye göz atın .

En çok bir koleksiyonu kullanarak getirdiğiniz sürece JOIN FETCH, iyi olacaksınız.

Birden çok sorgu kullanarak, Kartezyen Ürünü kullanamazsınız, çünkü ilk koleksiyon ikincil bir sorgu kullanılarak getirilir.

Yapabileceğiniz daha çok şey var

Eğer kullanıyorsanız FetchType.EAGERiçin zaman haritalama de strateji @OneToManyveya @ManyToManydernekler, o zaman kolayca ile bitebileceğini MultipleBagFetchException.

İstekli getirme kritik uygulama performansı sorunlarına yol açabilecek korkunç bir fikir olduğuFetchType.EAGER için geçiş yapmaktan daha iyidir .Fetchype.LAZY

Sonuç

Kaçının FetchType.EAGERve geçiş yok Listetmek Setbunu yaparken Hibernate yapacak sırf gizlemek MultipleBagFetchExceptionhalının altına. Her seferinde sadece bir koleksiyon getirin ve iyi olacaksınız.

Başlatmak için koleksiyonlarınız olduğu gibi aynı sayıda sorgu ile yaptığınız sürece, sorun yok. Koleksiyonları bir döngüde başlatmayın, çünkü bu performans için de kötü olan N + 1 sorgu sorunlarını tetikler .


Paylaşılan bilgi için teşekkürler. Ancak, DISTINCTbu çözümde performans katili. Kurtulmanın bir yolu var mı distinct? ( Set<...>bunun yerine dönmeye çalıştı , pek yardımcı olmadı)
Leonid Dashko

1
DISTINCT SQL deyimine gitmez. Bu yüzden PASS_DISTINCT_THROUGHolarak ayarlanmıştır false. DISTINCT'in JPQL'de 2 anlamı vardır ve burada, SQL tarafında değil, Java tarafında veri tekilleştirme yapmamız gerekir. Daha fazla ayrıntı için bu makaleye göz atın .
Vlad Mihalcea

Vlad, yardım için teşekkürler, gerçekten kullanışlı buluyorum. Ancak, sorunla ilgiliydi hibernate.jdbc.fetch_size(sonunda 350'ye ayarladım). Şans eseri, iç içe ilişkileri nasıl optimize edeceğinizi biliyor musunuz? Örneğin, varlık1 -> varlık2 -> varlık3.1, varlık 3.2 (burada varlık3.1 / 3.2'nin @OtoToMany ilişkileri olduğu)
Leonid Dashko

1
@LeonidDashko Veri getirmeyle ilgili birçok ipucu için Yüksek Performanslı Java Kalıcılığı kitabımdaki Getirme bölümüne göz atın .
Vlad Mihalcea

1
Hayır yapamazsın. Bunu SQL açısından düşünün. Kartezyen Ürün oluşturmadan birden fazla bire çok birleşmeye KATILAMAZSINIZ.
Vlad Mihalcea

31

Bu yazılarda ve diğerlerinde açıklanan her bir seçeneği denedikten sonra, düzeltmenin bir takip olduğu sonucuna vardım.

Her XToMany yerinde @ XXXToMany(mappedBy="parent", fetch=FetchType.EAGER) ve aradan sonra

@Fetch(value = FetchMode.SUBSELECT)

Bu benim için çalıştı


5
ekleme @Fetch(value = FetchMode.SUBSELECT)yeterliydi
user2601995

1
Bu yalnızca Hazırda Bekletme çözümüdür. Paylaşılan bir JPA kitaplığı kullanıyorsanız ne olur?
Michel,

3
Eminim demek istemedin, ama DaveRlz zaten aynı şeyi 3 yıl önce yazdı
phil294

21

Bunu düzeltmek için iç içe nesnenizin Setyerini almanız yeterlidir List.

@OneToMany
Set<Your_object> objectList;

ve kullanmayı unutma fetch=FetchType.EAGER

Çalışacak.

CollectionIdYalnızca listeye sadık kalmak istiyorsanız, Hazırda Beklet'te bir kavram daha vardır .

Ama bunu hatırlatmak olmaz underlaying ortadan kaldırmak Kartezyen Ürün tarafından açıklandığı gibi onun cevabını Vlad Mihalcea !



6

stand EAGER listelerini JPA'da tutabilir ve bunlardan en az birine JPA ek açıklaması @OrderColumn ekleyebilirsiniz (sipariş edilecek alanın açık bir şekilde olduğu gibi). Özel hazırda bekletme ek açıklamalarına gerek yoktur. Ancak, seçilen alan 0'dan başlayan değerler içermiyorsa listede boş öğeler oluşturabileceğini unutmayın.

 [...]
 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 @OrderColumn(name="orderIndex")
 private List<Child> children;
 [...]

Çocuklarda orderIndex alanını eklemelisiniz


2

List yerine Set'i denedik ve bu bir kabus: iki yeni nesne eklediğinizde, equals () ve hashCode () her ikisini de ayırt edemez ! Çünkü onların kimliği yok.

Eclipse gibi tipik araçlar Veritabanı tablolarından bu tür bir kod üretir:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((id == null) ? 0 : id.hashCode());
    return result;
}

JPA / Hibernate'in ne kadar berbat olduğunu açıklayan bu makaleyi de okuyabilirsiniz . Bunu okuduktan sonra, hayatımda en son ORM kullandığımı düşünüyorum.

Temelde ORM'in korkunç bir şey olduğunu söyleyen Domain Driven Design adamlarıyla da karşılaştım.


1

Saveral koleksiyona sahip çok karmaşık nesneleriniz varsa, hepsinin EAGER fetchType ile sahip olması iyi bir fikir olamazsa, LAZY'yi daha iyi kullanın ve koleksiyonları yüklemeniz gerektiğinde kullanın: Hibernate.initialize(parent.child)verileri almak için.


0

Benim için sorun iç içe EAGER getirmeleriydi.

Bir çözüm, iç içe alanları LAZY olarak ayarlamak ve iç içe geçmiş alanları yüklemek için Hibernate.initialize () yöntemini kullanmaktır:

x = session.get(ClassName.class, id);
Hibernate.initialize(x.getNestedField());

0

Sonunda, bu FetchType.EAGER ile birden fazla koleksiyon vardı, böyle oldu:

@ManyToMany(fetch = FetchType.EAGER, targetEntity = className.class)
@JoinColumn(name = "myClass_id")
@JsonView(SerializationView.Summary.class)
private Collection<Model> ModelObjects;

Ayrıca, koleksiyonlar aynı sütunda birleşiyordu.

Bu sorunu çözmek için, kullanım durumum için uygun olduğundan koleksiyonlardan birini FetchType.LAZY olarak değiştirdim.

İyi şanslar! ~ J


0

Her iki yorum yapan Fetchve LazyCollectionbazen projeyi çalıştırmak için yardımcı olur.

@Fetch(FetchMode.JOIN)
@LazyCollection(LazyCollectionOption.FALSE)

0

Hakkında iyi bir şey @LazyCollection(LazyCollectionOption.FALSE), bu ek açıklama içeren birkaç alanınFetchType.EAGER olabileceği, ancak böyle bir birlikte varlığın yasal olduğu durumlarda bile olabileceğidir.

Örneğin, Order listesini sahip olabilir OrderGroup(kısa bir) hem de bir liste Promotions(kısa). hiçbirine @LazyCollection(LazyCollectionOption.FALSE)neden olmadan her ikisinde de kullanılabilirLazyInitializationExceptionMultipleBagFetchException .

Benim durumumda rezil @Fetchsorunumu çözdüm MultipleBacFetchExceptionama sonra nedenleri LazyInitializationException,no Session hatayı .


-5

Bunu çözmek için yeni bir ek açıklama kullanabilirsiniz:

@XXXToXXX(targetEntity = XXXX.class, fetch = FetchType.LAZY)

Aslında, fetch'in varsayılan değeri FetchType.LAZY değeridir.


5
JPA3.0 mevcut değil.
holmis83
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.