MultipleBagFetchException - 두 개 이상의 OneToMany 관계에서 N+1 문제 최적화하기

2024. 11. 22. 00:44·프로젝트/Filmeet
목차
  1. 개요
  2. 문제 상황
  3. 조인 안 하고 값 조회 해보기
  4. N + 1 문제 해결하기 - MultipleBagFetchException 발생
  5. 두 개 이상의 OneToMany 관계에서 N+1 문제 해결하기
  6. 쿼리 분리하기
  7. 마무리 하며
  8. 참고

개요

이번 글에서는 두 개 이상의 OneToMany 관계에서 발생한 이슈와 이를 최적화하는 과정에 대해 작성하려 합니다.

문제 상황

OneToMany 관계의 엔티티들이 있습니다.

엔티티

Movie 엔티티

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Movie extends BaseEntity {

    @Id
    @Column(name = "movie_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    ...

    @Enumerated(EnumType.STRING)
    private FilmRatings filmRatings;

    @OneToMany(mappedBy = "movie")
    private List<Gallery> galleries = new ArrayList<>();

    @OneToMany(mappedBy = "movie")
    private List<MovieCountries> movieCountries = new ArrayList<>();

    @OneToMany(mappedBy = "movie")
    private List<MoviePersonnel> moviePersonnels = new ArrayList<>();

    @OneToMany(mappedBy = "movie")
    private List<MovieGenre> movieGenres = new ArrayList<>();
}

MoviePersonnel 엔티티

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class MoviePersonnel extends BaseTimeEntity {

    @Id
    @Column(name = "movie_personnel_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "personnel_id")
    private Personnel personnel;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "movie_id")
    private Movie movie;

    ...
}

Personnel 엔티티

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Personnel extends BaseTimeEntity {

    @Id
    @Column(name = "personnel_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    ...
}

Movie 엔티티는 4개의 엔티티와 일대다 연관관계에 있고 모두 LAZY Loading입니다.

여기서 galleries를 제외한 다른 3개의 엔티티는 다른 엔티티와 다대일 연관관계에 있습니다.

위에서 예시를 위해 일부만 가져온 MoviePersonnel와 Personnel 간의 관계라고 보시면 됩니다.

 

그리고 Movie 엔티티와 일대다 관계에 있는 galleries, movieCountries, moviePersonnels, movieGenres을 모두 가져와야 하는 서비스 메서드가 있습니다.

public MovieDetailResponse getMovieDetail(Long movieId, Long userId) {
    Movie movie = movieRepository.findMovieDetailInfo(movieId)
            .orElseThrow(MovieNotFoundException::new);

    ...
}

조인 안 하고 값 조회 해보기

우선 JPQL에서 다른 엔티티를 조인하지 않고 Movie 엔티티를 조회해 보겠습니다.

예시 JPQL

@Query("SELECT m FROM Movie m " +
        "WHERE m.id = :movieId AND m.isDeleted = false ")
Optional<Movie> findMovieDetailInfo(@Param("movieId") Long movieId);

default_batch_fetch_size: 1000 적용 안 함

    select
        m1_0.movie_id,
        ...
        m1_0.title 
    from
        movie m1_0 
    where
        m1_0.movie_id=? 
        and m1_0.is_deleted=0
2024-11-22T00:50:01.808+09:00 DEBUG 39090 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        m1_0.movie_id,
        m1_0.movie_countries_id,
        m1_0.countries_id,
        m1_0.created_at,
        m1_0.last_modified_at 
    from
        movie_countries m1_0 
    where
        m1_0.movie_id=?
2024-11-22T00:50:01.814+09:00 DEBUG 39090 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        c1_0.countries_id,
        c1_0.created_at,
        c1_0.last_modified_at,
        c1_0.nation 
    from
        countries c1_0 
    where
        c1_0.countries_id=?

    ...

    select
        c1_0.countries_id,
        c1_0.created_at,
        c1_0.last_modified_at,
        c1_0.nation 
    from
        countries c1_0 
    where
        c1_0.countries_id=?
2024-11-22T00:50:01.818+09:00 DEBUG 39090 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        m1_0.movie_id,
        m1_0.movie_personnel_id,
        m1_0.character_name,
        m1_0.created_at,
        m1_0.last_modified_at,
        m1_0.movie_position,
        m1_0.personnel_id 
    from
        movie_personnel m1_0 
    where
        m1_0.movie_id=?
2024-11-22T00:50:01.820+09:00 DEBUG 39090 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        p1_0.personnel_id,
        p1_0.created_at,
        p1_0.last_modified_at,
        p1_0.name,
        p1_0.profile_image 
    from
        personnel p1_0 
    where
        p1_0.personnel_id=?

    ...

    select
        p1_0.personnel_id,
        p1_0.created_at,
        p1_0.last_modified_at,
        p1_0.name,
        p1_0.profile_image 
    from
        personnel p1_0 
    where
        p1_0.personnel_id=?
2024-11-22T00:50:01.830+09:00 DEBUG 39090 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        g1_0.movie_id,
        g1_0.gallery_id,
        g1_0.created_at,
        g1_0.image_url,
        g1_0.last_modified_at 
    from
        gallery g1_0 
    where
        g1_0.movie_id=?
2024-11-22T00:50:01.831+09:00 DEBUG 39090 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        m1_0.movie_id,
        m1_0.movie_genre_id,
        m1_0.created_at,
        m1_0.genre_id,
        m1_0.last_modified_at 
    from
        movie_genre m1_0 
    where
        m1_0.movie_id=?
2024-11-22T00:50:01.832+09:00 DEBUG 39090 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        g1_0.genre_id,
        g1_0.created_at,
        g1_0.genre_type,
        g1_0.last_modified_at 
    from
        genre g1_0 
    where
        g1_0.genre_id=?
2024-11-22T00:50:01.833+09:00 DEBUG 39090 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        g1_0.genre_id,
        g1_0.created_at,
        g1_0.genre_type,
        g1_0.last_modified_at 
    from
        genre g1_0 
    where
        g1_0.genre_id=?

Movie와 연관된 다른 엔티티(movieCountries, galleries 등)는 기본적으로 지연 로딩(Lazy Loading)으로 설정되어 있어 Movie 엔티티를 가져올 때 동시에 다른 엔티티를 가져오지는 않습니다. 다만 애플리케이션 레벨에서 실제 연관된 엔티티의 데이터를 사용하기 위해 접근하면 실제 데이터가 필요하므로 해당 엔티티와 매핑되어 있는 테이블로 추가 select 쿼리가 실행되는 N + 1 문제가 발생합니다.

 

페치 조인도 안하고 default_batch_fetch_size 옵션도 안한 상태에서 Movie엔티티를 조회하고 애플리케이션 레벨단에서 Movie엔티티와 일대다 관계의 엔티티의 데이터를 사용하면 위와 같이 콘솔에 찍힌 것처럼 N + 1 문제가 발생합니다.

default_batch_fetch_size: 1000 적용

    select
        m1_0.movie_id,
        m1_0.average_rating,
        m1_0.created_at,
        m1_0.deleted_at,
        m1_0.film_ratings,
        m1_0.is_deleted,
        m1_0.like_counts,
        m1_0.last_modified_at,
        m1_0.plot,
        m1_0.poster_url,
        m1_0.release_date,
        m1_0.review_counts,
        m1_0.runtime,
        m1_0.title 
    from
        movie m1_0 
    where
        m1_0.movie_id=? 
        and m1_0.is_deleted=0
2024-11-22T00:49:11.940+09:00 DEBUG 39067 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        m1_0.movie_id,
        m1_0.movie_countries_id,
        m1_0.countries_id,
        m1_0.created_at,
        m1_0.last_modified_at 
    from
        movie_countries m1_0 
    where
        m1_0.movie_id=?
2024-11-22T00:49:11.947+09:00 DEBUG 39067 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        c1_0.countries_id,
        c1_0.created_at,
        c1_0.last_modified_at,
        c1_0.nation 
    from
        countries c1_0 
    where
        c1_0.countries_id in (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
2024-11-22T00:49:11.951+09:00 DEBUG 39067 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        m1_0.movie_id,
        m1_0.movie_personnel_id,
        m1_0.character_name,
        m1_0.created_at,
        m1_0.last_modified_at,
        m1_0.movie_position,
        m1_0.personnel_id 
    from
        movie_personnel m1_0 
    where
        m1_0.movie_id=?
2024-11-22T00:49:11.954+09:00 DEBUG 39067 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        p1_0.personnel_id,
        p1_0.created_at,
        p1_0.last_modified_at,
        p1_0.name,
        p1_0.profile_image 
    from
        personnel p1_0 
    where
        p1_0.personnel_id in (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
2024-11-22T00:49:11.959+09:00 DEBUG 39067 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        g1_0.movie_id,
        g1_0.gallery_id,
        g1_0.created_at,
        g1_0.image_url,
        g1_0.last_modified_at 
    from
        gallery g1_0 
    where
        g1_0.movie_id=?
2024-11-22T00:49:11.960+09:00 DEBUG 39067 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        m1_0.movie_id,
        m1_0.movie_genre_id,
        m1_0.created_at,
        m1_0.genre_id,
        m1_0.last_modified_at 
    from
        movie_genre m1_0 
    where
        m1_0.movie_id=?
2024-11-22T00:49:11.962+09:00 DEBUG 39067 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        g1_0.genre_id,
        g1_0.created_at,
        g1_0.genre_type,
        g1_0.last_modified_at 
    from
        genre g1_0 
    where
        g1_0.genre_id in (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)

이번에는 default_batch_fetch_size: 1000을 설정하고 쿼리를 실행해 보았습니다. 실행 결과, WHERE IN 절을 사용해 데이터를 조회하면서 기존에 N번 실행되던 쿼리가 1번으로 줄어든 것을 확인할 수 있었습니다. 실제 실행된 쿼리를 보면 IN 연산이 추가되어 반복 실행되던 쿼리가 단일 쿼리로 최적화됩니다.

 

여기서 Movie와 일대다 관계인 엔티티들에 대해서는 IN 연산이 추가되지 않은것에 의문이 생길 수 있습니다.

그 이유는 현재 조회된 Movie 엔티티가 1개뿐이기 때문입니다.

예를 들어 위 상황에서 Movie와 일대다 관계의 엔티티 데이터를 가져오기 위해 추가된 SQL문은 아래와 같습니다. 

아래와 같이 하나의 SQL만 발생해서 IN 연산을 사용할 필요가 없어 Movie엔티티와 일대다 관계에 있는 엔티티를 가져오기 위해 IN 연산이 추가 되지 않았습니다. 물론 조회된 Movie 엔티티가 1개가 아닌 복수개라면 일대다 관계에 있는 엔티티들에도 IN 연산이 추가 됩니다.

SELECT * FROM movie_countries WHERE movie_id = ?;
SELECT * FROM gallery WHERE movie_id = ?;
SELECT * FROM movie_genre WHERE movie_id = ?;
SELECT * FROM movie_personnel WHERE movie_id = ?;

N + 1 문제 해결하기 - MultipleBagFetchException 발생

이러한 N + 1 문제를 해결하기 위해서 페치 조인을 사용하거나 default_batch_fetch_size 옵션을 주로 사용합니다.

그렇다면 이번에 페치 조인을 사용해보겠습니다.

 

아래와 같이 JPQL문을 작성했고 실행 했습니다.

    @Query("SELECT m FROM Movie m " +
            "LEFT JOIN FETCH m.movieCountries mc " +
            "JOIN FETCH mc.countries c " +
            "LEFT JOIN FETCH m.moviePersonnels mp " +
            "JOIN FETCH mp.personnel p " +
            "LEFT JOIN FETCH m.movieGenres mg " +
            "JOIN FETCH mg.genre genre " +
            "LEFT JOIN FETCH m.galleries g " +
            "WHERE m.id = :movieId AND m.isDeleted = false ")
    Optional<Movie> findMovieDetailInfo(@Param("movieId") Long movieId);

하지만 이렇게 1:N 관계의 자식 테이블 여러곳에 Fetch Join을 사용하면 아래와 같이 에러가 발생합니다.

org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: [com.ureca.filmeet.domain.movie.entity.Movie.galleries, com.ureca.filmeet.domain.movie.entity.Movie.movieCountries]

이 문제는 2개 이상의 OneToMany 자식 테이블에 Fetch Join을 선언했을때 발생합니다.

JPA에서 Fetch Join의 조건은 다음과 같습니다.

  • ToOne은 몇개든 사용 가능합니다
  • ToMany는 1개만 가능합니다.

어떻게 하면 MultipleBagFetchException 에러 없이 N+1 문제를 최대한 회피할 수 있을까요?

두 개 이상의 OneToMany 관계에서 N+1 문제 해결하기

해결책 Hibernate default_batch_fetch_size

해결책은 하이버네이트의 default_batch_fetch_size 옵션에 있습니다.

다시 한번 JPA의 N+1 문제를 바라보겠습니다.

N+1 문제란 결국 부모 엔티티와 연관 관계가 있는 자식 엔티티들의 조회 쿼리가 문제입니다.

 

부모 엔티티의 Key 하나하나를 자식 엔티티 조회로 사용하기 때문인데요.
1개씩 사용되는 조건문을 in절로 묶어서 조회하면 어떨까요?

바로 이 개념으로 사용되는 것이 바로 hibernate.default_batch_fetch_size 옵션입니다.

해당 옵션은 지정된 수만큼 in절에 부모 Key를 사용하게 해줍니다.

즉, 1000개를 옵션값으로 지정하면 1000개 단위로 in절에 부모 Key가 넘어가서 자식 엔티티들이 조회되는 것이죠.
단순하게 생각해도 쿼리 수행수가 1/1000이 됩니다.

 

바로 코드를 만들고 콘솔에 찍히는 쿼리로 확인해보겠습니다.

우선 default_batch_fetch_size 옵션을 추가했습니다.

jpa:
  hibernate:
    ddl-auto: update
  properties:
    hibernate:
      format_sql: true
      default_batch_fetch_size: 1000
@Query("SELECT m FROM Movie m " +
        "LEFT JOIN FETCH m.moviePersonnels mp " +
        "JOIN FETCH mp.personnel p " +
        "WHERE m.id = :movieId AND m.isDeleted = false ")
Optional<Movie> findMovieDetailInfo(@Param("movieId") Long movieId);
    select
        m1_0.movie_id,
        ...
        m1_0.title 
    from
        movie m1_0 
    left join
        movie_personnel m2_0 
            on m1_0.movie_id=m2_0.movie_id 
    join
        personnel p1_0 
            on p1_0.personnel_id=m2_0.personnel_id 
    where
        m1_0.movie_id=? 
        and m1_0.is_deleted=0
2024-11-22T02:43:40.875+09:00 DEBUG 40255 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        m1_0.movie_id,
        m1_0.movie_countries_id,
        m1_0.countries_id,
        m1_0.created_at,
        m1_0.last_modified_at 
    from
        movie_countries m1_0 
    where
        m1_0.movie_id=?
2024-11-22T02:43:40.882+09:00 DEBUG 40255 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        c1_0.countries_id,
        c1_0.created_at,
        c1_0.last_modified_at,
        c1_0.nation 
    from
        countries c1_0 
    where
        c1_0.countries_id in (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
2024-11-22T02:43:40.888+09:00 DEBUG 40255 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        g1_0.movie_id,
        g1_0.gallery_id,
        g1_0.created_at,
        g1_0.image_url,
        g1_0.last_modified_at 
    from
        gallery g1_0 
    where
        g1_0.movie_id=?
2024-11-22T02:43:40.890+09:00 DEBUG 40255 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        m1_0.movie_id,
        m1_0.movie_genre_id,
        m1_0.created_at,
        m1_0.genre_id,
        m1_0.last_modified_at 
    from
        movie_genre m1_0 
    where
        m1_0.movie_id=?
2024-11-22T02:43:40.891+09:00 DEBUG 40255 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        g1_0.genre_id,
        g1_0.created_at,
        g1_0.genre_type,
        g1_0.last_modified_at 
    from
        genre g1_0 
    where
        g1_0.genre_id in (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)

발생 쿼리 개수

보시는것처럼 그동안 where xxx_id = ? 였던 조회 쿼리가 where xxx_id in (?, ?, ..) 으로 변경 된 것을 볼 수 있습니다.

 

앞서 조인 안하고 바로 조회했을때 발생한 총 20번이 수행되던 쿼리가 총 6번의 쿼리 수행으로 개선되었습니다.

20번과 6번이라고 하면 체감이 안될 수 있습니다.
이 개선은 Movie의 결과가 많으면 많을수록 쿼리 수행 횟수가 획기적으로 개선됩니다.

 

만약 1만개의 Movie (부모 엔티티)가 조회된다면

  • 옵션 미적용시
    • 총 20,001번의 쿼리가 수행됩니다.
    • Movie 조회 쿼리 1번
    • 각 Movie의 movieCountries 조회 쿼리가 10,000번
    • 각 Movie의 moviePersonnels 조회 쿼리가 10,000번
  • 옵션 적용시 (1000개)
    • 총 21번의 쿼리가 수행됩니다.
    • Movie 조회 쿼리 1번
    • 각 Movie의 movieCountries 조회 쿼리가 10번 (10,000 / 1,000)
    • 각 Movie의 moviePersonnels 조회 쿼리가 10번 (10,000 / 1,000)

최대 1,000 분의 1로 쿼리수를 줄일 수 있습니다.

Tip)
보통 옵션값을 1,000 이상 주지는 않습니다.
in절 파라미터로 1,000 개 이상을 주었을때 너무 많은 in절 파라미터로 인해 문제가 발생할수도 있기 때문입니다.
지금 옵션은 1000으로 두었기 때문에 movie가 1000개를 넘지 않으면 단일 쿼리로 수행 된다는 장점도 있습니다.

Tip)
같은 방법으로 Fetch 적용시 발생하는 페이징 문제도 동일하게 해결됩니다.
1:N 관계에서의 페이징 문제는 Join으로 인해 1에 대한 페이징이 정상작동 하지 않기 때문입니다.

결론

결론을 내리자면

  • hibernate.default_batch_fetch_size를 글로벌 설정으로 사용해 N+1 문제를 최대한 in 쿼리로 기본적인 성능을 보장하게 한다.
  • @OneToOne, @ManyToOne과 같이 1 관계의 자식 엔티티에 대해서는 모두 Fetch Join을 적용하여 한방 쿼리를 수행한다.
  • @OneToMany, @ManyToMany와 같이 N 관계의 자식 엔티티에 관해서는 가장 데이터가 많은 자식쪽에 Fetch Join을 사용한다.
    • Fetch Join이 없는 자식 엔티티에 관해서는 위에서 선언한 hibernate.default_batch_fetch_size 적용으로 100~1000개의 in 쿼리로 성능을 보장한다.

쿼리 분리하기

위 방식을 통해 쿼리 수를 6개로 줄였지만, 여전히 아쉬움이 남았습니다.
특히 MovieCountries를 조회할 때, Countries을 fetch join하여 한 번에 가져올 수 있고, Genre 역시 MovieGenre를 조회할 때 fetch join을 사용해 함께 조회할 수 있는데, 현재 방식은 최적화 측면에서 뭔가 부족하다는 느낌이 들었습니다.
 
이에 대해 고민한 끝에, 쿼리를 분리하고 fetch join을 적용하는 방식을 떠올렸습니다.
아래와 같이 MovieCountries와 MovieGenre를 조회할 때,
fetch join을 사용해 Countries과 Genre를 한 번에 조회하도록 변경했습니다.
이를 통해 쿼리 수를 기존 6개에서 4개로 줄이는 데 성공했습니다.

발생 쿼리 개수

@Query("SELECT m FROM Movie m " +
        "LEFT JOIN FETCH m.moviePersonnels mp " +
        "JOIN FETCH mp.personnel p " +
        "WHERE m.id = :movieId AND m.isDeleted = false ")
Optional<Movie> findMovieDetailInfo(@Param("movieId") Long movieId);
public interface MovieCountriesRepository extends JpaRepository<MovieCountries, Long> {

    @Query("SELECT mc FROM MovieCountries mc " +
            "JOIN FETCH mc.countries c " +
            "WHERE mc.movie.id = :movieId")
    List<MovieCountries> findMovieCountriesByMovieId(@Param("movieId") Long movieId);
}
public interface MovieGenreRepository extends JpaRepository<MovieGenre, Genre> {

    @Query("SELECT mg FROM MovieGenre mg " +
            "JOIN FETCH mg.genre g " +
            "WHERE mg.movie.id = :movieId")
    List<MovieGenre> findMovieGenresByMovieId(@Param("movieId") Long movieId);
}
    select
        m1_0.movie_id,
        ...
        m1_0.title 
    from
        movie m1_0 
    left join
        movie_personnel m2_0 
            on m1_0.movie_id=m2_0.movie_id 
    join
        personnel p1_0 
            on p1_0.personnel_id=m2_0.personnel_id 
    where
        m1_0.movie_id=? 
        and m1_0.is_deleted=0
2024-11-22T09:37:06.803+09:00 DEBUG 42700 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        g1_0.movie_id,
        g1_0.gallery_id,
        g1_0.created_at,
        g1_0.image_url,
        g1_0.last_modified_at 
    from
        gallery g1_0 
    where
        g1_0.movie_id=?
2024-11-22T09:37:06.807+09:00 DEBUG 42700 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        m1_0.movie_countries_id,
        c1_0.countries_id,
        c1_0.created_at,
        c1_0.last_modified_at,
        c1_0.nation,
        m1_0.created_at,
        m1_0.last_modified_at,
        m1_0.movie_id 
    from
        movie_countries m1_0 
    join
        countries c1_0 
            on c1_0.countries_id=m1_0.countries_id 
    where
        m1_0.movie_id=?
2024-11-22T09:37:06.810+09:00 DEBUG 42700 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        m1_0.movie_genre_id,
        m1_0.created_at,
        g1_0.genre_id,
        g1_0.created_at,
        g1_0.genre_type,
        g1_0.last_modified_at,
        m1_0.last_modified_at,
        m1_0.movie_id 
    from
        movie_genre m1_0 
    join
        genre g1_0 
            on g1_0.genre_id=m1_0.genre_id 
    where
        m1_0.movie_id=?

다만, 쿼리를 분리하는 방식은 Hibernate의 default_batch_fetch_size 방식과 비교했을 때, 데이터베이스를 여러 번 조회하게 되므로 DB 왕복 비용이 추가될 수 있습니다. 따라서 default_batch_fetch_size를 사용하는 방식과 쿼리를 분리해 조회하는 방식의 성능을 직접 비교한 후, 프로젝트에 더 적합한 방법을 선택하는 것이 좋습니다.

마무리 하며

이번 글에서는 두 개 이상의 OneToMany 관계에서 fetch join을 사용했을 때 발생한 MultipleBagFetchException을 해결하고, 이를 최적화하는 방식에 대해 작성했습니다.

 

두 개 이상의 OneToMany 관계에서 fetch join은 자주 사용될 수 있는 부분이기 때문에, 이 문제를 해결해 둔다면 추후 필요한 상황에서 유용하게 활용할 수 있을 것이라 생각합니다. 이번 경험을 통해 많은 것을 배울 수 있었고, 앞으로도 좋은 참고 자료가 될 것 같습니다.

참고

https://jojoldu.tistory.com/457

'프로젝트 > Filmeet' 카테고리의 다른 글

콘텐츠 기반 추천과 유사 사용자 기반 추천을 결합한 하이브리드 추천 시스템 만들기  (3) 2024.12.19
Redis 분산 락을 활용하여 동시성 문제 해결하기  (0) 2024.12.04
@Query with "not in" not work with empty List parameter  (0) 2024.12.02
동적 조건 처리를 위한 Querydsl 사용하기  (0) 2024.11.21
Filmeet 컬렉션 기능  (2) 2024.11.21
  1. 개요
  2. 문제 상황
  3. 조인 안 하고 값 조회 해보기
  4. N + 1 문제 해결하기 - MultipleBagFetchException 발생
  5. 두 개 이상의 OneToMany 관계에서 N+1 문제 해결하기
  6. 쿼리 분리하기
  7. 마무리 하며
  8. 참고
'프로젝트/Filmeet' 카테고리의 다른 글
  • Redis 분산 락을 활용하여 동시성 문제 해결하기
  • @Query with "not in" not work with empty List parameter
  • 동적 조건 처리를 위한 Querydsl 사용하기
  • Filmeet 컬렉션 기능
an_jjin
an_jjin
공부한 내용을 정리하는 개발 기록 블로그
an_jjin
An Devlog
an_jjin
전체
오늘
어제
  • 분류 전체보기
    • JAVA
      • 초급
      • 중급1
      • 중급2
      • 고급1
    • Spring
      • 핵심 원리 기본
      • MVC1
      • MVC2
      • DB1
      • 락
      • 디자인 패턴
      • AOP
    • JPA
      • JPA 기본
      • JPA 활용 1
      • JPA 활용 2
      • Spring Data Jpa
      • JPA 정리
    • 프로젝트
      • Filmeet
      • FitTrip
      • Kidsping
    • CS
      • 기술 면접 대비 CS 전공 핵심요약집
    • 네트워크
      • HTTP
      • WebSocket
    • 데이터베이스
    • 운영체제
      • 뇌를 자극하는 윈도우즈 시스템 프로그래밍
    • Git
    • Kafka
    • Docker
    • [LG유플러스] 유레카
    • 회고

블로그 메뉴

  • 홈
  • 태그
  • 방명록

링크

공지사항

인기 글

태그

spring websocket
redis
분산락
AOP
빈 후처리기
redlock
transactional outbox pattern
도커
redis 분산락
lg 유플러스 유레카 sw 교육 후기
캐시
lg 유플러스 유레카 sw 교육
스레드
Websocket
이벤트 응모 시스템
queryplan
db부하
lg 유플러스 유레카
lg 유레카
lg 유플러스 유레카 후기
선착순 응모 시스템
프록시 팩토리
동시성
before_commit
객체지향
STOMP
after_commit
트랜잭션 아웃박스 패턴
Transactional Outbox
자바

최근 댓글

최근 글

hELLO· Designed By정상우.v4.6.1
an_jjin
MultipleBagFetchException - 두 개 이상의 OneToMany 관계에서 N+1 문제 최적화하기

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.