Hibernate พ่น MultipleBagFetchException - ไม่สามารถดึงหลายถุงพร้อมกันได้


471

Hibernate แสดงข้อยกเว้นนี้ระหว่างการสร้าง SessionFactory

org.hibernate.loader.MultipleBagFetchException: ไม่สามารถดึงหลายถุงพร้อมกันได้

นี่คือกรณีทดสอบของฉัน:

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;

}

แล้วปัญหานี้ล่ะ? ฉันควรทำอย่างไร


แก้ไข

ตกลงปัญหาที่ฉันมีคือเอนทิตี "พาเรนต์" อื่นอยู่ในพาเรนต์ของฉันพฤติกรรมที่แท้จริงของฉันคือ:

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;

}

ไฮเบอร์เนตไม่ชอบคอลเลคชั่นสองอย่างFetchType.EAGERแต่นี่เป็นข้อผิดพลาดฉันไม่ได้ทำสิ่งผิดปกติ ...

เอาFetchType.EAGERออกParentหรือAnotherParentแก้ปัญหา แต่ฉันต้องการมันดังนั้นทางออกที่แท้จริงคือการใช้@LazyCollection(LazyCollectionOption.FALSE)แทนFetchType(ขอบคุณBozhoสำหรับการแก้ปัญหา)


ฉันจะถามคำถาม SQL ที่คุณต้องการสร้างที่จะดึงสองคอลเลกชันแยกพร้อมกันคืออะไร? ชนิดของ SQL ที่จะสามารถบรรลุสิ่งเหล่านี้อาจต้องการการเข้าร่วมแบบคาร์ทีเซียน (อาจไม่มีประสิทธิภาพสูง) หรือคอลัมน์ที่แยกจากกัน การไร้ความสามารถในการบรรลุถึงสิ่งนี้ใน SQL อย่างน่าเชื่อว่ามีประสิทธิภาพและมีอิทธิพลต่อการออกแบบ API
โทมัส W

@ThomasW ข้อความค้นหา sql ที่ควรสร้างคือ:select * from master; select * from child1 where master_id = :master_id; select * from child2 where master_id = :master_id
nurettin

1
คุณสามารถได้รับข้อผิดพลาด simillar หากคุณมีมากกว่าหนึ่งที่List<child>มีการfetchTypeกำหนดไว้มากกว่าหนึ่ง List<clield>
บิ๊ก Zed

คำตอบ:


555

ฉันคิดว่ารุ่นที่ใหม่กว่าของไฮเบอร์เนต (รองรับ JPA 2.0) ควรจัดการกับสิ่งนี้ แต่อย่างอื่นคุณสามารถแก้ไขได้ด้วยการใส่คำอธิบายประกอบในฟิลด์การรวบรวมด้วย:

@LazyCollection(LazyCollectionOption.FALSE)

อย่าลืมลบfetchTypeแอตทริบิวต์ออกจาก@*ToManyคำอธิบายประกอบ

แต่โปรดทราบว่าในกรณีส่วนใหญ่ a Set<Child>นั้นเหมาะสมกว่าList<Child>ดังนั้นถ้าคุณไม่ต้องการ a List- goSet

แต่เตือนว่าด้วยการใช้ชุดคุณจะไม่กำจัดผลิตภัณฑ์คาร์ทีเซียนที่อยู่ภายใต้คำอธิบายของVlad Mihalcea ในคำตอบของเขา !


4
แปลกมันได้ผลสำหรับฉัน คุณไม่ลบfetchTypeจาก@*ToMany?
Bozho

101
ปัญหาคือว่าคำอธิบายประกอบ JPA จะถูกแยกวิเคราะห์ไม่อนุญาตให้มีการรวบรวมมากกว่า 2 คอลเลกชันอย่างกระตือรือร้น แต่หมายเหตุประกอบแบบไฮเบอร์เนทอนุญาตเท่านั้น
Bozho

14
ความต้องการมากกว่า 1 EAGER ดูสมจริงมาก ข้อ จำกัด นี้เป็นเพียงการกำกับดูแล JPA หรือไม่ ฉันควรมองหาข้อกังวลอะไรเมื่อมีคนจำนวนมาก EAGERs?
AR3Y35

6
สิ่งนี้คือไฮเบอร์เนตไม่สามารถดึงทั้งสองคอลเล็กชันได้ด้วยการสืบค้นเดียว ดังนั้นเมื่อคุณสอบถามเอนทิตีพาเรนต์จะต้องเพิ่ม 2 เคียวรีต่อผลลัพธ์ซึ่งโดยปกติเป็นสิ่งที่คุณไม่ต้องการ
Bozho

7
มันจะเป็นการดีถ้ามีคำอธิบายว่าทำไมสิ่งนี้ถึงแก้ไขปัญหาได้
Webnet

290

เพียงเปลี่ยนจากListประเภทเป็นSetประเภท

แต่เตือนว่าคุณจะไม่กำจัดผลิตภัณฑ์คาร์ทีเซียนที่อยู่ใต้คำอธิบายโดยVlad Mihalcea ในคำตอบของเขา !


42
รายการและชุดไม่เหมือนกัน: ชุดไม่รักษาคำสั่งซื้อ
Matteo

17
LinkedHashSet คงไว้ซึ่งคำสั่ง
egallardo

15
นี่คือความแตกต่างที่สำคัญและเมื่อคุณคิดเกี่ยวกับมันถูกต้องทั้งหมด หลายต่อหลายคนทั่วไปที่ใช้งานโดยคีย์ต่างประเทศในฐานข้อมูลไม่ได้เป็นรายการจริงๆมันเป็นชุดเพราะคำสั่งซื้อจะไม่ถูกเก็บรักษาไว้ ดังนั้น Set จึงเหมาะสมกว่าจริงๆ ฉันคิดว่านั่นสร้างความแตกต่างในการจำศีลแม้ว่าฉันจะไม่รู้ว่าทำไม
fool4jesus

3
ฉันมีเหมือนกันไม่สามารถดึงถุงหลายใบได้พร้อมกันแต่ไม่ใช่เพราะมีคำอธิบายประกอบ ในกรณีของผมที่ผมทำร่วมซ้ายและ disjunctions *ToManyกับทั้งสอง การเปลี่ยนประเภทเพื่อSetแก้ไขปัญหาของฉันด้วย ทางออกที่ยอดเยี่ยมและเป็นระเบียบ นี่ควรเป็นคำตอบอย่างเป็นทางการ
L. Holanda

20
ฉันชอบคำตอบ แต่คำถามล้านดอลลาร์คือทำไม เหตุใดการตั้งค่าจึงไม่แสดงข้อยกเว้น ขอบคุณ
Hinotori

140

เพิ่มคำอธิบายประกอบ @Fetch เฉพาะ Hibernate ให้กับรหัสของคุณ:

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

สิ่งนี้ควรแก้ไขปัญหาที่เกี่ยวข้องกับ Hibernate bug HHH-1718


5
@DaveRlz ทำไม subSelect แก้ปัญหานี้ได้ ฉันลองใช้วิธีแก้ปัญหาและวิธีการทำงานแล้ว แต่ไม่ทราบว่าวิธีแก้ปัญหานี้ใช้งานอย่างไร
HakunaMatata

นี่คือคำตอบที่ดีที่สุดถ้าไม่มีSetเหตุผลจริงๆ มีOneToManyความสัมพันธ์เดียวโดยใช้Setผลลัพธ์ใน1+<# relationships>คิวรีโดยใช้FetchMode.SUBSELECTผลลัพธ์ใน1+1คิวรี นอกจากนี้การใช้คำอธิบายประกอบในคำตอบที่ได้รับการยอมรับ ( LazyCollectionOption.FALSE) จะทำให้มีการเรียกใช้แบบสอบถามเพิ่มเติม
mstrthealias

1
FetchType.EAGER ไม่ใช่โซลูชันที่เหมาะสมสำหรับสิ่งนี้ ต้องดำเนินการดึงข้อมูลโปรไฟล์ไฮเบอร์เนตและต้องแก้ปัญหา
Milinda Bandara

2
อีกสองคำตอบยอดนิยมไม่ได้แก้ปัญหาของฉัน สิ่งนี้ทำ ขอบคุณ!
Blindworks

3
ไม่มีใครรู้ว่าทำไม SUBSELECT ถึงแก้ไขได้ แต่เข้าร่วมไม่ได้
Innokenty

42

คำถามนี้เป็นธีมที่เกิดขึ้นซ้ำทั้งใน StackOverflow หรือฟอรัม Hibernate ดังนั้นฉันจึงตัดสินใจเปลี่ยนคำตอบเป็นบทความเช่นกัน

พิจารณาเรามีหน่วยงานดังต่อไปนี้:

ป้อนคำอธิบายรูปภาพที่นี่

และคุณต้องการดึงPostเอนทิตีพาเรนต์บางอย่างพร้อมกับcommentsและtagsคอลเลกชัน

หากคุณใช้มากกว่าหนึ่งJOIN FETCHคำสั่ง:

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

ไฮเบอร์เนตจะขว้างคนเลว:

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
]

Hibernate ไม่อนุญาตให้มีการเรียกถุงมากกว่าหนึ่งเพราะที่จะสร้างผลิตภัณฑ์ Cartesian

"ทางออก" ที่เลวร้ายที่สุด

ตอนนี้คุณจะพบคำตอบโพสต์บล็อกวิดีโอหรือแหล่งข้อมูลอื่น ๆ มากมายที่บอกให้คุณใช้SetแทนListคอลเล็กชันของคุณ

นั่นเป็นคำแนะนำที่แย่มาก อย่าทำอย่างนั้น!

ใช้SetsแทนListsจะทำให้MultipleBagFetchExceptionหายไป แต่ผลิตภัณฑ์คาร์ทีเซียนจะยังคงมีอยู่ซึ่งจริงๆแล้วยิ่งแย่ลงเพราะคุณจะพบปัญหาประสิทธิภาพการทำงานนานหลังจากที่คุณใช้ "แก้ไข" นี้

ทางออกที่เหมาะสม

คุณสามารถทำเคล็ดลับต่อไปนี้:

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

ในเคียวรี JPQL แรกอย่าdistinctไปที่คำสั่ง SQL นั่นเป็นเหตุผลที่เราตั้งค่าPASS_DISTINCT_THROUGHคำแนะนำการสอบถาม JPA falseไป

DISTINCT มีสองความหมายใน JPQL และที่นี่เราต้องการให้มันซ้ำซ้อนกับการอ้างอิงวัตถุ Java ที่ส่งคืนโดยgetResultListทางฝั่ง Java ไม่ใช่ด้าน SQL ตรวจสอบบทความนี้สำหรับรายละเอียดเพิ่มเติม

ตราบใดที่คุณดึงคอลเลคชั่นได้มากที่สุดหนึ่งชุดJOIN FETCHคุณก็ใช้ได้

ด้วยการใช้แบบสอบถามหลายรายการคุณจะหลีกเลี่ยงผลิตภัณฑ์คาร์ทีเซียนตั้งแต่การรวบรวมอื่น ๆ แต่การสืบค้นครั้งแรกจะถูกดึงข้อมูลโดยใช้คิวรีรอง

มีมากกว่าที่คุณสามารถทำได้

หากคุณกำลังใช้FetchType.EAGERกลยุทธ์ในเวลาทำแผนที่สำหรับ@OneToManyหรือสมาคมแล้วคุณได้อย่างง่ายดายสามารถจบลงด้วย@ManyToManyMultipleBagFetchException

คุณจะดีกว่าการเปลี่ยนจากFetchType.EAGERการFetchype.LAZYตั้งแต่การดึงข้อมูลความกระตือรือร้นที่เป็นความคิดที่แย่มากที่สามารถนำไปสู่ปัญหาประสิทธิภาพของโปรแกรมที่สำคัญ

ข้อสรุป

หลีกเลี่ยงFetchType.EAGERและไม่เปลี่ยนจากListเป็นSetเพราะจะทำให้ไฮเบอร์เนตซ่อนMultipleBagFetchExceptionอยู่ใต้พรม เรียกค้นได้ครั้งละหนึ่งคอลเลคชันและคุณจะไม่เป็นไร

ตราบใดที่คุณมีจำนวนข้อความค้นหาเท่ากันกับที่คุณมีการรวบรวมเพื่อเริ่มต้นคุณก็ไม่เป็นไร อย่าเริ่มต้นการรวบรวมในลูปเพราะจะทำให้เกิดปัญหาการสอบถาม N + 1ซึ่งเป็นผลเสียต่อประสิทธิภาพการทำงาน


ขอบคุณสำหรับความรู้ที่แบ่งปัน อย่างไรก็ตาม DISTINCTเป็นนักฆ่าประสิทธิภาพในการแก้ปัญหานี้ มีวิธีกำจัดdistinctไหม? (พยายามกลับมาSet<...>แทนไม่ได้ช่วยอะไรมาก)
Leonid

1
DISTINCT ไม่ได้ไปที่คำสั่ง SQL นั่นเป็นเหตุผลที่มีการตั้งค่าPASS_DISTINCT_THROUGH falseDISTINCT มี 2 ความหมายใน JPQL และที่นี่เราต้องการให้มันซ้ำซ้อนกับฝั่ง Java ไม่ใช่ฝั่ง SQL ตรวจสอบบทความนี้สำหรับรายละเอียดเพิ่มเติม
Vlad Mihalcea

วขอบคุณสำหรับความช่วยเหลือที่ฉันพบว่ามันมีประโยชน์จริงๆ อย่างไรก็ตามปัญหาเกี่ยวข้องกับhibernate.jdbc.fetch_size(ในที่สุดฉันตั้งค่าเป็น 350) โดยโอกาสที่คุณจะรู้วิธีการเพิ่มประสิทธิภาพความสัมพันธ์ซ้อนกัน? เช่น entity1 -> entity2 -> entity3.1, entity 3.2 (โดยที่ entity3.1 / 3.2 เป็นความสัมพันธ์ @OneToMany)
Leonid

1
@LeonidDashko ลองอ่านบทการดึงข้อมูลในหนังสือ Persistence Java ประสิทธิภาพสูงของฉันสำหรับเคล็ดลับมากมายที่เกี่ยวข้องกับการดึงข้อมูล
Vlad Mihalcea

1
ไม่คุณไม่สามารถ. คิดในแง่ของ SQL คุณไม่สามารถเข้าร่วมสมาคมแบบหนึ่งต่อหลายกลุ่มได้โดยไม่ต้องสร้างผลิตภัณฑ์คาร์ทีเซียน
Vlad Mihalcea

31

หลังจากลองใช้ทุกตัวเลือกเดียวที่อธิบายในโพสต์นี้และอื่น ๆ ฉันได้ข้อสรุปว่าการแก้ไขมีดังต่อไปนี้

ในทุกตำแหน่ง XToMany @ XXXToMany(mappedBy="parent", fetch=FetchType.EAGER) และตัวกลางหลังจากนั้น

@Fetch(value = FetchMode.SUBSELECT)

สิ่งนี้ใช้ได้สำหรับฉัน


5
การเพิ่ม@Fetch(value = FetchMode.SUBSELECT)มีเพียงพอ
user2601995

1
นี่เป็นวิธีแก้ปัญหาไฮเบอร์เนตเท่านั้น ถ้าคุณใช้ไลบรารี JPA ที่แชร์
มิเชล

3
ฉันแน่ใจว่าคุณไม่ได้ตั้งใจ แต่ DaveRlz ได้เขียนสิ่งเดียวกัน 3 ปีก่อนหน้านี้
phil294

21

ในการแก้ไขปัญหาเพียงใช้SetแทนListวัตถุที่ซ้อนกันของคุณ

@OneToMany
Set<Your_object> objectList;

และอย่าลืมใช้ fetch=FetchType.EAGER

มันจะทำงาน.

มีอีกหนึ่งแนวคิดCollectionIdในไฮเบอร์เนตหากคุณต้องการยึดติดกับรายการเท่านั้น

แต่เตือนว่าคุณจะไม่กำจัดผลิตภัณฑ์คาร์ทีเซียนที่อยู่ใต้คำอธิบายโดยVlad Mihalcea ในคำตอบของเขา !


11

ฉันพบโพสต์บล็อกที่ดีเกี่ยวกับพฤติกรรมของ Hibernate ในการแมปวัตถุชนิดนี้: http://blog.eyallupu.com/2010/06/hibernate-exception-sim พร้อมกันhtml


4
ยินดีต้อนรับสู่ Stack Overflow อย่างไรก็ตามคำตอบสำหรับลิงค์เท่านั้นไม่ได้รับการสนับสนุน คำถามที่พบบ่อยและโดยเฉพาะอย่างยิ่ง: stackoverflow.com/faq#deletion
femtoRgon

6

คุณสามารถเก็บรายชื่อบูธ EAGER ไว้ใน JPA และเพิ่มลงในคำอธิบายประกอบ JPA อย่างน้อยหนึ่งรายการ@OrderColumn (โดยมีชื่อของฟิลด์ที่จะสั่ง) ไม่จำเป็นต้องมีคำอธิบายประกอบจำศีลเฉพาะ แต่โปรดจำไว้ว่ามันสามารถสร้างองค์ประกอบว่างในรายการถ้าเขตข้อมูลที่เลือกไม่มีค่าเริ่มต้นจาก 0

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

ใน Children คุณควรเพิ่มฟิลด์ orderIndex


2

เราลองตั้งค่าแทนรายการและมันเป็นฝันร้าย: เมื่อคุณเพิ่มวัตถุใหม่สองรายการเท่ากับ () และhashCode () ไม่สามารถแยกแยะทั้งสองอย่างได้! เพราะพวกเขาไม่มีรหัสใด ๆ

เครื่องมือทั่วไปเช่น Eclipse สร้างรหัสชนิดนั้นจากตารางฐานข้อมูล:

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

คุณอาจอ่านบทความนี้เพื่ออธิบายว่า JPA / Hibernate เกิดความสับสนได้อย่างไร หลังจากอ่านสิ่งนี้ฉันคิดว่านี่เป็นครั้งสุดท้ายที่ฉันใช้ ORM ใด ๆ ในชีวิตของฉัน

ฉันได้พบกับพวก Domain Driven Design ที่โดยทั่วไปแล้ว ORM บอกว่าเป็นสิ่งที่แย่มาก


1

เมื่อคุณมีวัตถุที่ซับซ้อนมากเกินไปที่มีการเก็บสะสมแบบ saveral อาจเป็นความคิดที่ดีที่จะไม่ใช้ Eetch ทั้งหมด fetchType ให้ใช้ LAZY และเมื่อคุณจำเป็นต้องโหลดการใช้คอลเล็กชัน: Hibernate.initialize(parent.child)เพื่อดึงข้อมูล


0

สำหรับฉันแล้วปัญหาเกิดขึ้นกับEAGER ที่ซ้อนกันดึงข้อมูล

ทางออกหนึ่งคือการตั้งค่าฟิลด์ที่ซ้อนเป็นLAZYและใช้ Hibernate.initialize () เพื่อโหลดฟิลด์ที่ซ้อนกัน:

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

0

ในตอนท้ายของฉันเกิดขึ้นเมื่อฉันมีคอลเลกชันหลายรายการที่มี FetchType.EAGER เช่นนี้

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

นอกจากนี้คอลเล็กชันยังเข้าร่วมในคอลัมน์เดียวกัน

เพื่อแก้ไขปัญหานี้ฉันได้เปลี่ยนคอลเล็กชันหนึ่งเป็น FetchType.LAZY เนื่องจากมันไม่เป็นไรสำหรับกรณีใช้งานของฉัน

โชคดี! ~ J


0

การแสดงความคิดเห็นทั้งสองFetchและLazyCollectionบางครั้งช่วยในการเรียกใช้โครงการ

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

0

สิ่งหนึ่งที่ดีเกี่ยวกับ@LazyCollection(LazyCollectionOption.FALSE)คือหลายสาขาที่มีคำอธิบายประกอบนี้สามารถอยู่ร่วมกันได้ในขณะที่FetchType.EAGERไม่สามารถแม้ในสถานการณ์ที่การอยู่ร่วมกันดังกล่าวเป็นสิ่งที่ถูกต้อง

ตัวอย่างเช่นรายการOrderอาจมีรายการOrderGroup(แบบย่อ) และรายการPromotions(แบบย่อ) @LazyCollection(LazyCollectionOption.FALSE)สามารถนำมาใช้ทั้งไม่ก่อให้เกิดค่าLazyInitializationExceptionMultipleBagFetchException

ในกรณีของฉัน@Fetchไม่ได้แก้ปัญหาของฉันMultipleBacFetchExceptionแต่แล้วทำให้เกิดข้อผิดพลาดที่LazyInitializationExceptionน่าอับอายno Session


-5

คุณสามารถใช้คำอธิบายประกอบใหม่เพื่อแก้ปัญหานี้:

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

ที่จริงแล้วค่าเริ่มต้นของการดึงข้อมูลคือ FetchType.LAZY เช่นกัน


5
JPA3.0 ไม่มีอยู่
holmis83
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.