Java & Spring/트러블슈팅

MultipleBagFetchException(두개의 Fetch Join)

DJ.Kang 2024. 10. 8. 13:40

- 문제상황

        List<Todo> results = jpaQueryFactory
                .selectFrom(todo)
                .leftJoin(todo.managers).fetchJoin()
                .leftJoin(todo.comments).fetchJoin()
                .where(condition)
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch();
  • todo조회 시 manager와 comment의 개수를 출력하면서 N+1문제를 해결하기 위해 Fetch Join을 연속으로 두개 사용
  • MultipleBagFetchException 발생
    -> 2개 이상의 OneToMany 자식 테이블에 Fetch Join을 선언했을때 발생
    MultipleBagFetchException의 원인
    Hibernate는 여러 개의 List 타입 @OneToMany 또는 @ManyToMany 관계를 Fetch Join으로 동시에 로딩할 때, 내부적으로 "중복된 레코드"를 제거하는 방식 때문에 발생, List는 순서를 보장해야 하기 때문에, 데이터베이스에서 조회된 결과를 그대로 유지해야 하며, 중복 제거 시 복잡성이 증가 이로 인해, Hibernate는 여러 컬렉션을 List로 Fetch Join하는 경우 예외를 던지게됨.

 

- 해결방법

 

1. 객체수가 많은 comment는 fetchjoin으로 대비 작은 manager은 BatchSize로 해결

   List<Todo> results = jpaQueryFactory
                .selectFrom(todo)
                .leftJoin(todo.managers)
                .leftJoin(todo.comments).fetchJoin()
                .where(condition)
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch();
           
    // Todo Entity
    public class Todo extends Timestamped {             
        @OneToMany(mappedBy = "todo", cascade = CascadeType.PERSIST)
        @BatchSize(size = 10)
        private List<Manager> managers = new ArrayList<>();
    }

 

2. OneToMany부분을 Set으로 받아 처리

    @OneToMany(mappedBy = "todo", cascade = CascadeType.REMOVE)
    private Set<Comment> comments = new HashSet<>();

    @OneToMany(mappedBy = "todo", cascade = CascadeType.PERSIST)
    private Set<Manager> managers = new HashSet<>();