Hibernate ném NhiềuBagFetchException - không thể tìm nạp đồng thời nhiều túi


471

Hibernate ném ngoại lệ này trong quá trình tạo SessionFactory:

org.hibernate.loader.Mult MônBagFetchException: không thể tải cùng lúc nhiều túi

Đây là trường hợp thử nghiệm của tôi:

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;

}

Làm thế nào về vấn đề này? Tôi có thể làm gì?


BIÊN TẬP

OK, vấn đề tôi gặp phải là một thực thể "cha mẹ" khác ở trong cha mẹ tôi, hành vi thực sự của tôi là thế này:

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 không thích hai bộ sưu tập với FetchType.EAGER, nhưng đây có vẻ là một lỗi, tôi không làm những điều khác thường ...

Loại bỏ FetchType.EAGERtừ Parenthoặc AnotherParentgiải quyết vấn đề, nhưng tôi cần nó, vì vậy giải pháp thực sự là để sử dụng @LazyCollection(LazyCollectionOption.FALSE)thay vì FetchType(nhờ Bozho cho các giải pháp).


Tôi sẽ hỏi, truy vấn SQL nào mà bạn hy vọng tạo ra sẽ truy xuất hai bộ sưu tập riêng biệt cùng một lúc? Các loại SQL có thể đạt được những điều này sẽ yêu cầu tham gia cartesian (có khả năng không hiệu quả cao) hoặc UNION của các cột rời rạc (cũng xấu). Có lẽ việc không thể đạt được điều này trong SQL theo cách sạch sẽ và hiệu quả đã ảnh hưởng đến thiết kế API.
Thomas W

@ThomasW Đây là các truy vấn sql cần tạo:select * from master; select * from child1 where master_id = :master_id; select * from child2 where master_id = :master_id
Nurettin

1
Bạn có thể nhận được một lỗi simillar nếu bạn có nhiều hơn một List<child>với fetchTypeđịnh nghĩa cho nhiều hơn một List<clield>
Big Zed

Câu trả lời:


555

Tôi nghĩ rằng một phiên bản mới hơn của ngủ đông (hỗ trợ JPA 2.0) sẽ xử lý việc này. Nhưng nếu không, bạn có thể xử lý nó bằng cách chú thích các trường bộ sưu tập với:

@LazyCollection(LazyCollectionOption.FALSE)

Nhớ xóa fetchTypethuộc tính khỏi @*ToManychú thích.

Nhưng lưu ý rằng trong hầu hết các trường hợp, a Set<Child>thích hợp hơn List<Child>, vì vậy trừ khi bạn thực sự cần một List- đi choSet

Nhưng hãy nhớ rằng với việc sử dụng các bộ, bạn sẽ không loại bỏ Sản phẩm Cartesian được mô tả bởi Vlad Mihalcea trong câu trả lời của mình !


4
lẻ, nó đã làm việc cho tôi Bạn đã loại bỏ fetchTypekhỏi @*ToMany?
Bozho

101
vấn đề là các chú thích JPA được phân tích cú pháp không cho phép nhiều hơn 2 bộ sưu tập được tải một cách háo hức. Nhưng các chú thích dành riêng cho ngủ đông cho phép nó.
Bozho

14
Nhu cầu về hơn 1 EAGER dường như hoàn toàn thực tế. Có phải giới hạn này chỉ là sự giám sát của JPA? Những mối quan tâm tôi nên tìm kiếm khi có EAGER mulipl là gì?
AR3Y35

6
điều này là, ngủ đông không thể tìm nạp hai bộ sưu tập với một truy vấn. Vì vậy, khi bạn truy vấn thực thể cha mẹ, nó sẽ cần thêm 2 truy vấn cho mỗi kết quả, đó thường là điều bạn không muốn.
Bozho

7
Thật tuyệt khi có một lời giải thích về lý do tại sao điều này giải quyết vấn đề.
Webnet

290

Đơn giản chỉ cần thay đổi từ Listloại này sang Setloại khác.

Nhưng hãy nhớ rằng bạn sẽ không loại bỏ Sản phẩm Cartesian lót như mô tả của Vlad Mihalcea trong câu trả lời của mình !


42
Danh sách và Tập hợp không giống nhau: một tập hợp không giữ trật tự
Matteo

17
LinkedHashset giữ trật tự
egallardo

15
Đây là một sự khác biệt quan trọng và, khi bạn nghĩ về nó, hoàn toàn chính xác. Nhiều-một-một điển hình được thực hiện bởi khóa ngoại trong DB thực sự không phải là Danh sách, đó là Tập hợp vì thứ tự không được giữ nguyên. Vì vậy, Set thực sự phù hợp hơn. Tôi nghĩ rằng điều đó tạo ra sự khác biệt trong ngủ đông, mặc dù tôi không biết tại sao.
lừa4jesus

3
Tôi đã có cùng một lúc không thể lấy nhiều túi nhưng không phải vì chú thích. Trong trường hợp của tôi, tôi đã thực hiện các phép nối trái và bất đồng với hai người *ToMany. Thay đổi loại để Setgiải quyết vấn đề của tôi quá. Giải pháp tuyệt vời và gọn gàng. Đây phải là câu trả lời chính thức.
L. Holanda

20
Tôi thích câu trả lời, nhưng câu hỏi triệu đô la là: Tại sao? Tại sao với Set không hiển thị ngoại lệ? Cảm ơn
Hinotori

140

Thêm chú thích @Fetch dành riêng cho Hibernate vào mã của bạn:

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

Điều này sẽ khắc phục sự cố, liên quan đến lỗi Hibernate HHH-1718


5
@DaveRlz tại sao subSelect giải quyết vấn đề này. Tôi đã thử giải pháp của bạn và nó hoạt động, nhưng không biết vấn đề đã được giải quyết bằng cách sử dụng như thế nào?
HakunaMatata

Đây là câu trả lời tốt nhất trừ khi Setthực sự có ý nghĩa. Có một OneToManymối quan hệ bằng cách sử dụng Setkết quả trong 1+<# relationships>các truy vấn, trong đó sử dụng FetchMode.SUBSELECTkết quả trong 1+1các truy vấn. Ngoài ra, sử dụng chú thích trong câu trả lời được chấp nhận ( LazyCollectionOption.FALSE) sẽ khiến nhiều truy vấn được thực thi hơn nữa.
mstrthealias

1
FetchType.EAGER không phải là một giải pháp thích hợp cho việc này. Cần tiến hành với Hồ sơ tìm nạp Hibernate và cần giải quyết nó
Milinda Bandara

2
Hai câu trả lời hàng đầu khác không giải quyết được vấn đề của tôi. Cái này đã làm. Cảm ơn bạn!
Blindworks

3
Có ai biết tại sao SUBSELECT sửa nó không, nhưng THAM GIA thì không?
Innokenty

42

Câu hỏi này đã là một chủ đề định kỳ trên cả StackOverflow hoặc diễn đàn Hibernate, vì vậy tôi cũng quyết định biến câu trả lời thành một bài viết .

Xem xét chúng tôi có các thực thể sau đây:

nhập mô tả hình ảnh ở đây

Và, bạn muốn tìm nạp một số Postthực thể cha mẹ cùng với tất cả commentstags bộ sưu tập .

Nếu bạn đang sử dụng nhiều hơn một JOIN FETCHchỉ thị:

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 sẽ ném khét tiếng:

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 không cho phép tìm nạp nhiều hơn một túi vì điều đó sẽ tạo ra một sản phẩm của Cartesian .

"Giải pháp" tồi tệ nhất

Bây giờ, bạn sẽ tìm thấy rất nhiều câu trả lời, bài đăng trên blog, video hoặc các tài nguyên khác bảo bạn sử dụng Setthay vìList bộ sưu tập của bạn.

Đó là lời khuyên khủng khiếp. Đừng làm vậy!

Sử dụng Setsthay vì Listssẽ làm choMultipleBagFetchException mất, nhưng Sản phẩm của Cartesian vẫn sẽ ở đó, điều này thực sự còn tồi tệ hơn, vì bạn sẽ phát hiện ra vấn đề hiệu suất rất lâu sau khi bạn áp dụng "cách khắc phục" này.

Giải pháp thích hợp

Bạn có thể thực hiện các mẹo sau:

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

Trong truy vấn JPQL đầu tiên, distinctKHÔNG đi đến câu lệnh SQL. Đó là lý do tại sao chúng tôi đặt PASS_DISTINCT_THROUGHgợi ý truy vấn JPA thành false.

DISTINCT có hai ý nghĩa trong JPQL và ở đây, chúng ta cần nó để lặp lại các tham chiếu đối tượng Java được trả về bởi getResultListphía Java, không phải phía SQL. Kiểm tra bài viết này để biết thêm chi tiết.

Miễn là bạn lấy tối đa một bộ sưu tập bằng cách sử dụng JOIN FETCH, bạn sẽ ổn thôi.

Bằng cách sử dụng nhiều truy vấn, bạn sẽ tránh được Sản phẩm của Cartesian vì bất kỳ bộ sưu tập nào khác nhưng truy vấn đầu tiên được tìm nạp bằng truy vấn phụ.

Có nhiều hơn bạn có thể làm

Nếu bạn đang sử dụng FetchType.EAGERchiến lược vào thời điểm lập bản đồ cho @OneToManyhay @ManyToManyhiệp hội, sau đó bạn có thể dễ dàng kết thúc với mộtMultipleBagFetchException .

Bạn tốt hơn hết là chuyển từ FetchType.EAGERsang Fetchype.LAZYháo hức là một ý tưởng tồi tệ có thể dẫn đến các vấn đề hiệu năng ứng dụng quan trọng .

Phần kết luận

Tránh FetchType.EAGERvà không chuyển từ Listđể Setchỉ vì làm như vậy sẽ làm cho Hibernate giấu MultipleBagFetchExceptiondưới thảm. Chỉ lấy một bộ sưu tập tại một thời điểm và bạn sẽ ổn thôi.

Miễn là bạn làm điều đó với cùng số lượng truy vấn như bạn có các bộ sưu tập để khởi tạo, bạn sẽ ổn. Chỉ không khởi tạo các bộ sưu tập trong một vòng lặp, vì điều đó sẽ kích hoạt các vấn đề truy vấn N + 1 , điều này cũng không tốt cho hiệu suất.


Cảm ơn các kiến ​​thức đã chia sẻ. Tuy nhiên, DISTINCTlà kẻ giết người hiệu suất trong giải pháp này. Có cách nào để thoát khỏi distinct? (cố gắng quay lại Set<...>thay vào đó, không giúp được gì nhiều)
Leonid Dashko

1
DISTINCT không đi đến câu lệnh SQL. Đó là lý do tại sao PASS_DISTINCT_THROUGHđược đặt thành false. DISTINCT có 2 ý nghĩa trong JPQL và ở đây, chúng ta cần nó để lặp lại ở phía Java, không phải phía SQL. Kiểm tra bài viết này để biết thêm chi tiết.
Vlad Mihalcea

Vlad, cảm ơn vì sự giúp đỡ tôi thấy nó thực sự hữu ích. Tuy nhiên, vấn đề có liên quan đến hibernate.jdbc.fetch_size(cuối cùng tôi đặt nó thành 350). Ngẫu nhiên, bạn có biết làm thế nào để tối ưu hóa các mối quan hệ lồng nhau? Ví dụ: entity1 -> entity2 -> entity3.1, entity 3.2 (trong đó thực thể3.1 / 3.2 là quan hệ @OneToMany)
Leonid Dashko

1
@LeonidDashko Hãy xem chương Tìm nạp trong cuốn sách Kiên trì Java hiệu suất cao của tôi để biết nhiều mẹo liên quan đến tìm nạp dữ liệu.
Vlad Mihalcea

1
Không, bạn không thể. Hãy suy nghĩ về nó theo SQL. Bạn không thể THAM GIA nhiều hiệp hội một-nhiều mà không tạo ra Sản phẩm của Cartesian.
Vlad Mihalcea

31

Sau khi thử với mọi tùy chọn duy nhất được mô tả trong bài đăng này và các tùy chọn khác, tôi đã đi đến kết luận rằng bản sửa lỗi là như sau.

Ở mọi nơi XToMany @ XXXToMany(mappedBy="parent", fetch=FetchType.EAGER) và trung gian sau

@Fetch(value = FetchMode.SUBSELECT)

Điều này làm việc cho tôi


5
thêm @Fetch(value = FetchMode.SUBSELECT)là đủ
user2601995 25/2/2015

1
Đây là một giải pháp duy nhất của Hibernate. Nếu bạn đang sử dụng thư viện JPA dùng chung thì sao?
Michel

3
Tôi chắc chắn bạn không có ý đó, nhưng DaveRlz đã viết điều tương tự 3 năm trước
phil294

21

Để khắc phục, chỉ cần Setthay thế Listcho đối tượng lồng nhau của bạn.

@OneToMany
Set<Your_object> objectList;

và đừng quên sử dụng fetch=FetchType.EAGER

nó sẽ làm việc

Có một khái niệm nữa CollectionId trong Hibernate nếu bạn chỉ muốn gắn bó với danh sách.

Nhưng hãy nhớ rằng bạn sẽ không loại bỏ Sản phẩm Cartesian lót như mô tả của Vlad Mihalcea trong câu trả lời của mình !



6

bạn có thể giữ các danh sách EAGER gian hàng trong JPA và thêm vào ít nhất một trong số chúng chú thích JPA @OrderColumn (rõ ràng là tên của một trường sẽ được đặt hàng). Không cần chú thích ngủ đông cụ thể. Nhưng hãy nhớ rằng nó có thể tạo các phần tử trống trong danh sách nếu trường được chọn không có giá trị bắt đầu từ 0

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

Trong Trẻ em, bạn nên thêm trường orderIndex


2

Chúng tôi đã thử Đặt thay vì Danh sách và đó là một cơn ác mộng: khi bạn thêm hai đối tượng mới, bằng () và hashCode () không thể phân biệt cả hai đối tượng! Bởi vì họ không có id nào cả.

các công cụ điển hình như Eclipse tạo ra loại mã đó từ các bảng Cơ sở dữ liệu:

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

Bạn cũng có thể đọc bài viết này giải thích chính xác mức độ gây rối của JPA / Hibernate. Sau khi đọc nó, tôi nghĩ rằng đây là lần cuối cùng tôi sử dụng bất kỳ ORM nào trong đời.

Tôi cũng đã gặp những người thiết kế tên miền mà về cơ bản nói ORM là một điều tồi tệ.


1

Khi bạn có các đối tượng quá phức tạp với bộ sưu tập tiết kiệm, không thể có tất cả chúng với EAGER fetchType, sử dụng LAZY tốt hơn và khi bạn thực sự cần tải các bộ sưu tập, hãy sử dụng: Hibernate.initialize(parent.child)để tìm nạp dữ liệu.


0

Đối với tôi, vấn đề là có EAGER lồng nhau .

Một giải pháp là đặt các trường lồng nhau thành LAZY và sử dụng Hibernate.initialize () để tải (các) trường lồng nhau:

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

0

Cuối cùng, điều này xảy ra khi tôi có nhiều bộ sưu tập với FetchType.EAGER, như thế này:

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

Ngoài ra, các bộ sưu tập đã được tham gia trên cùng một cột.

Để giải quyết vấn đề này, tôi đã thay đổi một trong các bộ sưu tập thành FetchType.LAZY vì trường hợp sử dụng của tôi vẫn ổn.

Chúc may mắn! ~ J


0

Bình luận cả hai FetchLazyCollectionđôi khi giúp chạy dự án.

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

0

Một điều tốt @LazyCollection(LazyCollectionOption.FALSE)là một số lĩnh vực với chú thích này có thể cùng tồn tại trong khi FetchType.EAGERkhông thể, ngay cả trong các tình huống cùng tồn tại như vậy là hợp pháp.

Ví dụ, Ordermột danh sách có thể có một danh sách OrderGroup(một ngắn) cũng như một danh sách Promotions(cũng ngắn). @LazyCollection(LazyCollectionOption.FALSE)có thể được sử dụng trên cả hai mà không gây ra LazyInitializationExceptioncả MultipleBagFetchException.

Trong trường hợp của tôi @Fetchđã giải quyết vấn đề của tôi MultipleBacFetchExceptionnhưng sau đó gây ra LazyInitializationException, no Sessionlỗi khét tiếng .


-5

Bạn có thể sử dụng một chú thích mới để giải quyết điều này:

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

Trên thực tế, giá trị mặc định của fetch cũng là FetchType.LAZY.


5
JPA3.0 không tồn tại.
holmis83
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.