티스토리 뷰

JPA

[JAVA]JPA N+1 문제란?(해결방안)

개발도 운동만큼 2023. 3. 29. 23:36
반응형

N+1 쿼리 문제의 원인?

Spring Data JPA 에서 제공하는 Repository의 findAll(), findById()등과 같은 메소드를 사용하면 바로 DB에 SQL 쿼리를 날리는 것이 아닙니다.

JPQL 이라는 객체지향 쿼리 언어를 생성, 실행시킨 후 JPA는 이것을 분석해서 SQL을 생성, 실행하는 동작에서 N+1 쿼리 문제가 발생한다.

JPQL 입장에서는 LAZY로딩, EAGER 로딩과 같은 글로벌 패치 전략을 신경쓰지 않고 JPQL만 사용해서 SQL을 생성한다.

N+1 쿼리 문제는 언제 발생할까?

발생하는 경우는 다음과 같은 2가지 경우가 있다.

두개의 Entity가 1:N 관계를 가지며 JPQL로 객체를 조회할때

  • EAGER 전략으로 데이터를 가지고 오는 경우
  • LAZY 전략으로 데이터를 가져온 후 가져온 데이터에서 하위 Entity를 다시 조회하는 경우 (보통 이 경우가 가장 많이 발생 됨)

아래의 코드는 1:N 관계를 만들기 위해 하나의 앨범이 많은 노래를 가질 수 있도록 Entity를 생성하고 관계를 연결한 코드

Class.Album

        @Entity
          public class Album {
          @Id
          @GeneratedValue(strategy = GenerationType.IDENTITY)
          private long id;

          @Column(nullable = false)
          private String albumTitle;

          @Column(nullable = false)
          private String locales;

          // @OneToMany(mappedBy = "album", cascade = CascadeType.ALL, fetch = FetchType.EAGER) // 
          @OneToMany(mappedBy = "album", cascade = CascadeType.ALL, fetch = FetchType.LAZY) // 
          private List<Song> songs = new ArrayList<>();
          }

Class.Song

          @Entity
            public class Song {
                @Id
                @GeneratedValue(strategy = GenerationType.IDENTITY)
                private long id;

                @Column(nullable = false)
                private String title;

                @Column(nullable = false)
                private int track;

                @Column(nullable = false)
                private int length;

                @ManyToOne(fetch = FetchType.LAZY)
                @JoinColumn(name = "album_id")
                private Album album;
            }

Test Code

          @Test
          @Transactional // 테스팅에서 LAZY 전략시 필수
          public void N1_쿼리테스트_2() throws Exception{
              List<Album> albums = albumRepository.findAll();
              for (Album album : albums) {
                  System.out.println(album.getSongs().size()); // Song에 접근 !
              }
          }

LAZY 방식

N+1 발생!

처음엔 Album 리스트만 조회 했다면 Album Entity 에서 하위 엔티티인 Song Entity로 접근 했기 때문에 LAZY 로딩이 일어나면서 N+1 문제 발생
이미지

N+1 문제 해결 방안

Fatch Join

    @Query("select Distinct a from Album as a join fetch a.songs"
    List<Album> findAllJoinFetch();

    @Test
    @Transactional // 테스팅에서 LAZY 전략시 사용해야 동작
    public void FetchJoin_테스트() throws Exception{
        List<Album> albums = albumRepository.findAllJoinFetch();
        for (Album album : albums) {
            System.out.println(album.getSongs().size()); // Song에 접근 !
        }
    }

이미지

Fetch Join 에는 2가지 단점이 있다.

  1. JPA 가 제공하는 Pagable 기능 사용 불가(페이징 API)

  2. 1:N 관계가 2개인 Entity를 Fetch Join 사용 불가

    임시 해결법은 List -> Set으로 자료구조를 변경 하는 것

EntityGraph

    public interface PostRepository extends JpaRepository<Post, Long> {

    @EntityGraph(attributePaths = "comments")
    @Query("select p from Post p")
    List<Post> findAllEntityGraph();
    }

위 메서드를 통해 목록을 가져오면 Left Outer Join을 통해 하위 패키지도 하나의 쿼리로 가져온다

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함