오늘의하루

org.hibernate.loader.MultipleBagFetchException 원인 및 해결 본문

Spring/JPA

org.hibernate.loader.MultipleBagFetchException 원인 및 해결

오늘의하루_master 2024. 2. 15. 17:25

예외 발생 이유

이 예외는 Fetch Join을 할 때 발생하게 되는데 원인은 2개 이상의 컬렉션이 있는 상태에서 Fetch Join을 하는 경우에 발생하게 된다.

어쩌다 발생했을까?

C Entity와 B Entity는 N:1 관계이고 B Entity와 A Entity는 N:1 관계이고 모두 지연 로딩(LAZY)로 되어있다.

A Entity에서 특정 Id값으로 조회하여 조회되는 데이터를 Json 형식으로 보내려고 했다.

처음에 별 생각 없이 A Entity 자체를 보내게 되었는데 이때 Stack Overflow가 발생하고 넘어간 데이터를 보니 재귀현상이 발생하고 있었고 이 문제를 해결하기 위해 Fetch Join을 사용하면서 발견했다.

해결 방안

  1. 쿼리를 분리하자!
    • 여러 개의 컬렉션을 가져와야 할 경우, 두 개의 별도의 쿼리를 실행하여 각각의 컬렉션을 가져올 수 있습니다.
  2. Fetch Join 대신 배치 조회 사용하자!
    • 페치 조인 대신 배치 조회(batch fetching)를 사용하여 연관된 엔티티를 일괄로 가져올 수 있습니다. 이는 여러 개의 컬렉션을 패치 조인으로 가져오는 대신 엔티티 식별자를 사용하여 한 번에 여러 엔티티를 한꺼번에 가져오는 것입니다.
  3. Entity를 DTO로 변환하자!
    • 이 방법은 지나가면서 보게 된 글이 떠올라서 해본 방법이며 이 방법이 성능에 좋은지는 현재 알 수 없지만 이 방법으로도 해결이 가능하다. 해당 방법은 Entity와 DTO를 별도로 만들어서 Entity를 DTO로 변환 시켜주면 지연 로딩으로 나가지 않았던 쿼리들이 나가기는 하지만 해당 문제를 해결할 수 있었다.

최종적으로 어떻게 했을까?

Hibernate에 대해 검색하던 도중 통계자료를 보여주는 hibernate.generate_statistics라는 걸 알게 되었다.

해당 옵션을 활성화 한 후 비교한 결과이다.

  1. No Fetch Join & All Entity Convert DTO
    1. 준비 및 실행 시간 : 0.0024989 초
    2. 총 쿼리 개수 : 4 개
  2. ONE Fetch Join & All Entity Convert DTO
    1. 비 및 실행 시간 : 0.0015834 초
    2. 총 쿼리 개수 : 3 개

해당 통계가 정말 성능를 완벽하게 나타내는지는 아직 알 수 없지만 우선 1개의 컬렉션만 Fetch Join을 한 후 나머지 1개는 지연 로딩 상태로 두고 DTO로 변환하는것으로 결정하였다.

결정하게 된 계기는 우선 쿼리 수가 1개지만 줄었으며 준비 및 실행시간도 평균적으로 약 34~36%정도 빠르게 나타났기 때문이다.

Comments