최대 절전 모드에서 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의 종류는 데카르트 조인 (잠재적으로 비효율적) 또는 UNION (비 연속 열)도 필요합니다. 아마도 깨끗하고 효율적인 방식으로 SQL에서이를 달성 할 수없는 것은 API 디자인에 영향을 미쳤습니다.
Thomas 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>
빅 데이빗

답변:


555

최신 버전의 최대 절전 모드 (JPA 2.0 지원)가이를 처리해야한다고 생각합니다. 그러나 그렇지 않으면 컬렉션 필드에 주석을 달아서 해결할 수 있습니다.

@LazyCollection(LazyCollectionOption.FALSE)

에서 fetchType속성 을 제거해야 합니다@*ToMany주석 .

그러나 대부분의 경우 a Set<Child>가보다 적합 List<Child>하므로 실제로List 에 대한 이동을 -Set

그러나 세트를 사용하면 Vlad Mihalcea가 그의 답변에서 설명한 것처럼 밑받침이되는 직교 곱을 제거 하지 않습니다 !


4
홀수, 그것은 나를 위해 일했다. 당신은 제거 했 fetchType으로부터 @*ToMany?
Bozho

101
문제는 JPA 어노테이션이 2 개 이상의로드 된 콜렉션을 허용하지 않도록 구문 분석된다는 것입니다. 그러나 동면 특정 주석이 허용합니다.
Bozho December

14
하나 이상의 EAGER에 대한 요구는 완전히 현실적으로 보입니다. 이 제한은 단지 JPA 감독입니까? 여러 가지 EAGER를 가질 때 고려해야 할 사항은 무엇입니까?
AR3Y35

6
문제는 최대 절전 모드가 하나의 쿼리로 두 컬렉션을 가져올 수 없다는 것입니다. 따라서 부모 엔터티를 쿼리 할 때 결과 당 2 개의 추가 쿼리가 필요하며 이는 일반적으로 원하지 않는 것입니다.
Bozho

7
이것이 문제를 해결하는 이유에 대한 설명이 있으면 좋겠습니다.
웹넷

290

간단히 List유형 에서 유형으로 변경하십시오 Set.

그러나 Vlad Mihalcea가 그의 답변에서 설명한 것처럼 밑받침이되는 직교 곱을 제거 하지는 않을 것입니다 !


42
리스트와 세트는 같은 것이 아니다 : 세트는 순서를 유지하지 않는다
Matteo

17
LinkedHashSet은 순서를 유지합니다
egallardo

15
이것은 중요한 차이점이며, 생각할 때 완전히 정확합니다. DB에서 외래 키로 구현되는 일반적인 다대 일은 실제로 List가 아니며 순서가 유지되지 않기 때문에 Set입니다. 따라서 Set이 더 적합합니다. 왜 그런지 모르겠지만 최대 절전 모드에서 차이를 만듭니다.
fool4jesus

3
여러 백을 동시에 가져올 수는 없지만 주석으로 인해 동일한 것을 가질 수는 없었습니다 . 내 경우에는 두 사람과의 왼쪽 조인과 분리를하고 *ToMany있었습니다. 유형을 변경하면 Set문제가 해결되었습니다. 우수하고 깔끔한 솔루션. 공식적인 답변이어야합니다.
L. Holanda

20
나는 그 대답을 좋아했지만 백만 달러짜리 질문은 : 왜? Set으로 예외가 표시되지 않는 이유는 무엇입니까? 감사합니다
Hinotori

140

코드에 Hibernate 고유의 @Fetch 주석을 추가하십시오.

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

이것은 최대 절전 모드 버그 HHH-1718 과 관련된 문제를 해결해야합니다.


5
@DaveRlz 왜 subSelect가이 문제를 해결합니까? 귀하의 솔루션과 그 작동을 시도했지만 이것을 사용하여 문제가 어떻게 해결되었는지 알 수 없습니까?
HakunaMatata

이것이 Set실제로 의미가 없다면 최선의 대답 입니다. 단일 데 OneToMany사용하여 A 관계 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 product를 생성하기 때문에 하나 이상의 bag을 가져올 수 없다 .

최악의 "솔루션"

이제 많은 답변, 블로그 게시물, 비디오 또는 기타 SetList 컬렉션에 대신 를 찾을 수 있습니다.

그것은 끔찍한 충고입니다. 하지마!

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_THROUGHJPA 쿼리 힌트를로 설정 한 이유 false입니다.

DISTINCT는 JPQL에서 두 가지 의미를 지니고 있으며 여기서 getResultListSQL 측이 아닌 Java 측에서 리턴 된 Java 오브젝트 참조를 중복 제거해야합니다 . 확인 이 문서 자세한 내용을.

를 사용하여 최대 하나의 컬렉션을 가져 오는 한 JOIN FETCH괜찮습니다.

여러 쿼리를 사용하면 다른 컬렉션이 있지만 첫 번째 컬렉션은 보조 쿼리를 사용하여 가져 오기 때문에 Cartesian Product를 피할 수 있습니다.

할 수있는 일이 더 있습니다

FetchType.EAGER매핑 시간 @OneToMany또는 @ManyToMany연결 시 전략을 사용하는 경우 쉽게MultipleBagFetchException .

당신은에서 전환 것이 더 낫다 FetchType.EAGER에 대한 Fetchype.LAZY열망 페치가 있기 때문에 중요한 애플리케이션의 성능 문제가 발생할 수 있습니다 끔찍한 생각 .

결론

FetchType.EAGER와 전환하지 않습니다 ListSet이렇게하면 최대 절전 모드를 만들해서이를 숨길 MultipleBagFetchException카펫 아래에. 한 번에 하나의 컬렉션 만 가져 오면 괜찮을 것입니다.

컬렉션을 초기화 할 때와 같은 수의 쿼리로 수행하는 한 괜찮습니다. 루프에서 컬렉션을 초기화하지 마십시오. N + 1 쿼리 문제가 발생 하기 때문에 성능에 좋지 않습니다.


공유 된 지식에 감사드립니다. 그러나이 DISTINCT솔루션에서 성능이 저하됩니다. 제거하는 방법이 distinct있습니까? (반환하려고 Set<...>많은 도움이되지 않았다, 대신)
레오 니드 Dashko

1
DISTINCT는 SQL 문으로 이동하지 않습니다. 이것이 PASS_DISTINCT_THROUGH로 설정된 이유 입니다 false. DISTINCT는 JPQL에서 두 가지 의미를 가지며 여기서 SQL 측이 아닌 Java 측에서 중복 제거해야합니다. 확인 이 문서 자세한 내용을.
Vlad Mihalcea

블라드, 정말 유용하다는 사실에 감사드립니다. 그러나이 문제는 관련이 있습니다 hibernate.jdbc.fetch_size(결국 350으로 설정했습니다). 우연히, 중첩 관계를 최적화하는 방법을 알고 있습니까? 예 : entity1-> entity2-> entity3.1, entity 3.2 (여기서 entity3.1 / 3.2는 @OneToMany 관계 임)
Leonid Dashko

1
@LeonidDashko 고성능 Java Persistence 책 의 페치 장에서 데이터 페치 관련 팁을 확인하십시오.
Vlad Mihalcea

1
아니 당신은 할 수 없습니다. SQL 측면에서 생각하십시오. 카티 전 곱을 생성하지 않고 여러 일대 다 연결에 참여할 수 없습니다.
Vlad Mihalcea

31

이 게시물과 다른 게시물에 설명 된 모든 단일 옵션으로 시도한 후에 수정 사항은 다음과 같습니다.

모든 XToMany 장소에서 @ XXXToMany(mappedBy="parent", fetch=FetchType.EAGER) 및 중간 이후

@Fetch(value = FetchMode.SUBSELECT)

이것은 나를 위해 일했다


5
추가 @Fetch(value = FetchMode.SUBSELECT)충분
2601995

1
이것은 최대 절전 모드 전용 솔루션입니다. 공유 JPA 라이브러리를 사용중인 경우 어떻게합니까?
Michel

3
나는 당신이 의미하지는 않았지만, DaveRlz는 이미 3 년 전에 같은 것을 썼습니다
phil294 April

21

이를 고치려면 단순히 중첩 된 객체 Set대신 List사용하십시오.

@OneToMany
Set<Your_object> objectList;

그리고 사용하는 것을 잊지 마세요 fetch=FetchType.EAGER

작동합니다.

하나 더 개념이 있습니다 CollectionId목록만을 고수하고 싶다면 최대 절전 모드에 .

그러나 Vlad Mihalcea가 그의 답변에서 설명한 것처럼 밑받침이되는 직교 곱을 제거 하지는 않을 것입니다 !



6

부스 EAGER 목록을 JPA에 유지하고 이들 중 하나에 JPA 주석 @OrderColumn을 추가 할 수 있습니다 (주문할 필드 이름 포함). 특정 최대 절전 모드 주석이 필요하지 않습니다. 그러나 선택한 필드에 0부터 시작하는 값이 없으면 목록에서 빈 요소를 만들 수 있습니다.

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

어린이에서 orderIndex 필드를 추가해야합니다


2

우리는 List 대신 Set 을 시도했지만 악몽입니다. equals ()와 hashCode 라는 두 개의 새로운 객체를 추가하면 ()는 를 구별하지 못합니다! 그들은 ID가 없기 때문에.

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을 마지막으로 사용하는 것이라고 생각합니다.

또한 기본적으로 ORM이 끔찍하다고 말하는 도메인 기반 디자인 담당자를 만났습니다.


1

saveral collection을 사용하여 너무 복잡한 오브젝트가 EAGER 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.