• 먼저 생각해 볼 수 있는 방법은 @EntityGraph로 한 번에 다 가져올 수 있습니다.
  • join fetch을 사용하여 한 번에 쿼리 할 수 있습니다.
  • hibernate의 default batch를 사용하는 방법이 있습니다.

3. Hibernate의 default batch를 사용하는 방법

spring boot를 사용하여 application.yml에 다음 항목을 설정하면 됩니다.

spring:

  jpa:

    properties:

      hibernate.default_batch_fetch_size: 1000

===== LAZY
Hibernate: 
    select
        post0_.id as id1_1_,
        post0_.title as title2_1_ 
    from
        post post0_
2020-06-15 15:32:53.655 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_1_] : [BIGINT]) - [1]
2020-06-15 15:32:53.659 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([title2_1_] : [VARCHAR]) - [첫 포스트]
2020-06-15 15:32:53.660 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_1_] : [BIGINT]) - [4]
2020-06-15 15:32:53.660 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([title2_1_] : [VARCHAR]) - [내가 2등~]
2020-06-15 15:32:53.667 TRACE 9690 --- [    Test worker] org.hibernate.type.CollectionType        : Created collection wrapper: [com.icatapark.jpa.post.entity.Post.comments#1]
2020-06-15 15:32:53.667 TRACE 9690 --- [    Test worker] org.hibernate.type.CollectionType        : Created collection wrapper: [com.icatapark.jpa.post.entity.Post.comments#4]
Hibernate: 
    select
        comments0_.post_id as post_id3_0_1_,
        comments0_.id as id1_0_1_,
        comments0_.id as id1_0_0_,
        comments0_.comment as comment2_0_0_,
        comments0_.post_id as post_id3_0_0_ 
    from
        comment comments0_ 
    where
        comments0_.post_id in (
            ?, ?
        )
2020-06-15 15:32:53.672 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [4]
2020-06-15 15:32:53.673 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [BIGINT] - [1]
2020-06-15 15:32:53.676 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_0_] : [BIGINT]) - [2]
2020-06-15 15:32:53.677 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_0_0_] : [VARCHAR]) - [첫 댓글~! ]
2020-06-15 15:32:53.677 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_0_0_] : [BIGINT]) - [1]
2020-06-15 15:32:53.678 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_0_1_] : [BIGINT]) - [1]
2020-06-15 15:32:53.678 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_1_] : [BIGINT]) - [2]
2020-06-15 15:32:53.683 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_0_] : [BIGINT]) - [3]
2020-06-15 15:32:53.683 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_0_0_] : [VARCHAR]) - [두번째야~]
2020-06-15 15:32:53.684 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_0_0_] : [BIGINT]) - [1]
2020-06-15 15:32:53.684 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_0_1_] : [BIGINT]) - [1]
2020-06-15 15:32:53.685 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_1_] : [BIGINT]) - [3]
2020-06-15 15:32:53.685 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_0_] : [BIGINT]) - [5]
2020-06-15 15:32:53.685 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_0_0_] : [VARCHAR]) - [좋아요~]
2020-06-15 15:32:53.686 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_0_0_] : [BIGINT]) - [4]
2020-06-15 15:32:53.686 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_0_1_] : [BIGINT]) - [4]
2020-06-15 15:32:53.686 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_1_] : [BIGINT]) - [5]
2020-06-15 15:32:53.687 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_0_] : [BIGINT]) - [6]
2020-06-15 15:32:53.688 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_0_0_] : [VARCHAR]) - [감사합니다.]
2020-06-15 15:32:53.688 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_0_0_] : [BIGINT]) - [4]
2020-06-15 15:32:53.689 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_0_1_] : [BIGINT]) - [4]
2020-06-15 15:32:53.694 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_1_] : [BIGINT]) - [6]
2020-06-15 15:32:53.695 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_0_] : [BIGINT]) - [7]
2020-06-15 15:32:53.696 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_0_0_] : [VARCHAR]) - [다음글 기대할께요.]
2020-06-15 15:32:53.696 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_0_0_] : [BIGINT]) - [4]
2020-06-15 15:32:53.696 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_0_1_] : [BIGINT]) - [4]
2020-06-15 15:32:53.697 TRACE 9690 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_1_] : [BIGINT]) - [7]
LAZY : [[Comment(id=2, comment=첫 댓글~! ), Comment(id=3, comment=두번째야~)], [Comment(id=5, comment=좋아요~), Comment(id=6, comment=감사합니다.), Comment(id=7, comment=다음글 기대할께요.)]]

프록시를 통해서 연관된 entity의 실제 값을 가져오는 DB 쿼리를 in 절로 수정되어서 한 번에 쿼리가 됩니다.

그리고 @BatchSize 어노테이션을 class나 method, member variable에 각각 적용도 가능합니다. (실제 테스트 결과 @OneToMany에 적용했을 때만 동작했습니다. Entity class에 @BatchSize를 적용하는 방법은 좀 더 확인이 필요한 것 같습니다.)

@Entity
@BatchSize(size=100)
class Product {
    @OneToMany
    @BatchSize(size = 10) /
    Set<Product> getProducts() { ... };
}

 

'Programming > JPA' 카테고리의 다른 글

N + 1 문제 해결 2  (0) 2020.06.11
N + 1 문제 해결 1  (0) 2020.06.11
N + 1 문제 원인  (0) 2020.06.05
왜 JPA를 써야할까?  (0) 2020.06.05
JPA 기본 Annotation 정리  (7) 2019.07.04
  • 먼저 생각해 볼 수 있는 방법은 @EntityGraph로 한 번에 다 가져올 수 있습니다.
  • join fetch을 사용하여 한 번에 쿼리할 수 있습니다.
  • hibernate의 default fetch를 사용하는 방법이 있습니다.

앞서 살펴본 EntityGraph에 이어 join fetch을 살펴 보겠습니다.

2. Join Fetch를 사용하는 방법

먼저 살펴본 방법(2020/06/11 - [Programming/JPA] - N + 1 문제 해결 1)은 JpaRepository에서 원하는 쿼리로 바뀌도록 @EntityGraph를 이용하여 가이드 했다면 이번 방법은 JPQL을 사용하여 직접적으로 해결하는 방법입니다.

public interface PostRepository extends JpaRepository<Post, Long> {

    @Query("select p from Post p join fetch p.comments")
    List<Post> findAllWithComments();

}

해결편1에서 사용한 소스에 PostRepository만 바뀌었습니다. "join fetch p.comments"부분이 추가 되었습니다.

@SpringBootTest
class PostRepositoryTest {

    @Autowired
    private PostService postService;
    @Autowired
    private PostRepository postRepository;

    @BeforeEach
    public void setup() {
        Post post = new Post();
        post.setTitle("첫 포스트");

        Comment comment1 = new Comment();
        comment1.setComment("첫 댓글~! ");
        comment1.setPost(post);
        post.getComments().add(comment1);

        Comment comment2 = new Comment();
        comment2.setComment("두번째야~");
        post.getComments().add(comment2);
        comment2.setPost(post);

        Post post2 = new Post();
        post2.setTitle("내가 2등~");

        Comment comment3 = new Comment();
        comment3.setComment("좋아요~");
        comment3.setPost(post2);
        post2.getComments().add(comment3);

        Comment comment4 = new Comment();
        comment4.setComment("감사합니다.");
        comment4.setPost(post2);
        post2.getComments().add(comment4);

        Comment comment5 = new Comment();
        comment5.setComment("다음글 기대할께요.");
        comment5.setPost(post2);
        post2.getComments().add(comment5);

        postRepository.save(post);
        postRepository.save(post2);
    }

    @Test
    void testNPlusOne() {
        // lazy loading
        System.out.println("===== LAZY");
        System.out.println("LAZY : " + postService.findAllComments());

        // Join fetch
        System.out.println("===== JoinFetch");
        System.out.println("JoinFetch : " + postService.findAllWithComments());
    }
}

결과는 아래와 같이 inner join으로 실행되었죠...

===== JoinFetch
Hibernate: 
    select
        post0_.id as id1_3_0_,
        comments1_.id as id1_2_1_,
        post0_.title as title2_3_0_,
        comments1_.comment as comment2_2_1_,
        comments1_.post_id as post_id3_2_1_,
        comments1_.post_id as post_id3_2_0__,
        comments1_.id as id1_2_0__ 
    from
        post post0_ 
    inner join
        comment comments1_ 
            on post0_.id=comments1_.post_id
2020-06-11 17:58:26.842 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_0_] : [BIGINT]) - [1]
2020-06-11 17:58:26.843 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [2]
2020-06-11 17:58:26.843 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([title2_3_0_] : [VARCHAR]) - [첫 포스트]
2020-06-11 17:58:26.844 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [첫 댓글~! ]
2020-06-11 17:58:26.844 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [1]
2020-06-11 17:58:26.844 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0__] : [BIGINT]) - [1]
2020-06-11 17:58:26.845 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0__] : [BIGINT]) - [2]
2020-06-11 17:58:26.845 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_0_] : [BIGINT]) - [1]
2020-06-11 17:58:26.846 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [3]
2020-06-11 17:58:26.846 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [두번째야~]
2020-06-11 17:58:26.846 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [1]
2020-06-11 17:58:26.847 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0__] : [BIGINT]) - [1]
2020-06-11 17:58:26.849 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0__] : [BIGINT]) - [3]
2020-06-11 17:58:26.850 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_0_] : [BIGINT]) - [4]
2020-06-11 17:58:26.850 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [5]
2020-06-11 17:58:26.854 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([title2_3_0_] : [VARCHAR]) - [내가 2등~]
2020-06-11 17:58:26.855 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [좋아요~]
2020-06-11 17:58:26.855 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [4]
2020-06-11 17:58:26.857 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0__] : [BIGINT]) - [4]
2020-06-11 17:58:26.857 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0__] : [BIGINT]) - [5]
2020-06-11 17:58:26.857 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_0_] : [BIGINT]) - [4]
2020-06-11 17:58:26.857 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [6]
2020-06-11 17:58:26.858 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [감사합니다.]
2020-06-11 17:58:26.858 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [4]
2020-06-11 17:58:26.859 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0__] : [BIGINT]) - [4]
2020-06-11 17:58:26.859 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0__] : [BIGINT]) - [6]
2020-06-11 17:58:26.861 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_0_] : [BIGINT]) - [4]
2020-06-11 17:58:26.862 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [7]
2020-06-11 17:58:26.863 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [다음글 기대할께요.]
2020-06-11 17:58:26.863 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [4]
2020-06-11 17:58:26.863 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0__] : [BIGINT]) - [4]
2020-06-11 17:58:26.864 TRACE 3302 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0__] : [BIGINT]) - [7]
JoinFetch : [[Comment(id=2, comment=첫 댓글~! ), Comment(id=3, comment=두번째야~)], [Comment(id=2, comment=첫 댓글~! ), Comment(id=3, comment=두번째야~)], [Comment(id=5, comment=좋아요~), Comment(id=6, comment=감사합니다.), Comment(id=7, comment=다음글 기대할께요.)], [Comment(id=5, comment=좋아요~), Comment(id=6, comment=감사합니다.), Comment(id=7, comment=다음글 기대할께요.)], [Comment(id=5, comment=좋아요~), Comment(id=6, comment=감사합니다.), Comment(id=7, comment=다음글 기대할께요.)]]

@EntityGraph를 사용한 1편에서는 left outer join으로 실행 되었던 것을 기억하시나요?

그렇습니다. 이 두 방법은 실행 방식이 다릅니다.

  • @EntityGraph : left outer join
  • join fetch : inner join

그런데 두 방법 모두 comments가 중복되어서 출력될 수 있습니다. 카테시안 곱이 되기 때문입니다.

Cartesian Product 해결

코너속의 코너

먼저 두 가지 방법이 있습니다.

  1. collection을 List가 아닌 Set으로 바꾸는 방법입니다.
    private Set<Comment> comments = new LinkedHashSet<>();
    (참고로 HashSet으로 하면 중복 제거는 되는데 순서가 보장이 안됩니다.)
  2. @Query에서 distinct를 사용하는 방법입니다.
    @Query("select distinct p from Post p join fetch p.comments")
    List<Post> findAllWithComments();

개인적으로는 List를 사용하는 것이 더 좋아 보입니다. 왜냐하면 JpaRepository의 interface들은 다 List이기 때문입니다. 하지만 상황에 따라 Set을 쓰지 않는다는 보장은 없으니 여러모로 알고 있는 것이 이로워 보입니다. 그리고 항상 느끼는 것이지만 발생된 query와 결과는 꼭 확인이 필요합니다.

 

github source : https://github.com/aloftcat/blog-code/tree/master/jpa/NPlusOne

'Programming > JPA' 카테고리의 다른 글

N + 1 문제 해결 3  (0) 2020.06.15
N + 1 문제 해결 1  (0) 2020.06.11
N + 1 문제 원인  (0) 2020.06.05
왜 JPA를 써야할까?  (0) 2020.06.05
JPA 기본 Annotation 정리  (7) 2019.07.04

정말 많이 접하는 문제입니다. 왜 발생하고 처리하는 방법은 뭐가 있는지 정리하려 합니다.

우선 왜 이런 문제가 생기는지 정리합니다. 언제나 문제는 원인부터 알아야 올바르게 대처하니까요.

 

포스트(Post)와 댓글(Comment)와의 관계를 예로 작성해 보았습니다.

@Getter@Setter
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor
@Entity
@ToString
public class Post {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL)
    private List<Comment> comments = new ArrayList<>();

}

@Setter@Getter
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor
@Entity
@ToString(exclude = "post")
public class Comment {

    @Id
    @GeneratedValue
    private Long id;

    private String comment;

    @ManyToOne
    private Post post;
}
public interface PostRepository extends JpaRepository<Post, Long> {
}

public interface CommentRepository extends JpaRepository<Comment, Long> {
}
@Slf4j
@Service
public class PostService {

    private final PostRepository postRepository;

    public PostService(PostRepository postRepository) {
        this.postRepository = postRepository;
    }

    @Transactional(readOnly = true)
    public List<String> findAllComments() {
        return getAllComments(postRepository.findAll());
    }

    /**
     * Lazy Load를 실행하기 위해 모든 comment 순회
     */
    private List<String> getAllComments(List<Post> posts) {
        return posts.stream()
                .map(a -> a.getComments().toString())
                .collect(Collectors.toList());
    }
}

 

test code는 junit5을 사용하였습니다.

@SpringBootTest
class PostRepositoryTest {

    @Autowired
    private PostService postService;
    @Autowired
    private PostRepository postRepository;

    @BeforeEach
    public void setup() {
        Post post = new Post();
        post.setTitle("첫 포스트");

        Comment comment1 = new Comment();
        comment1.setComment("첫 댓글~! ");
        comment1.setPost(post);
        post.getComments().add(comment1);

        Comment comment2 = new Comment();
        comment2.setComment("두번째야~");
        post.getComments().add(comment2);
        comment2.setPost(post);

        Post post2 = new Post();
        post2.setTitle("내가 2등~");

        Comment comment3 = new Comment();
        comment3.setComment("좋아요~");
        comment3.setPost(post2);
        post2.getComments().add(comment3);

        Comment comment4 = new Comment();
        comment4.setComment("감사합니다.");
        comment4.setPost(post2);
        post2.getComments().add(comment4);

        Comment comment5 = new Comment();
        comment5.setComment("다음글 기대할께요.");
        comment5.setPost(post2);
        post2.getComments().add(comment5);

        postRepository.save(post);
        postRepository.save(post2);
    }

    @Test
    void testNPlusOne() {
        System.out.println(postService.findAllComments());
    }
Hibernate: 
    select
        post0_.id as id1_3_,
        post0_.title as title2_3_ 
    from
        post post0_
2020-06-05 11:50:15.206 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_] : [BIGINT]) - [1]
2020-06-05 11:50:15.210 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([title2_3_] : [VARCHAR]) - [첫 포스트]
2020-06-05 11:50:15.211 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_3_] : [BIGINT]) - [4]
2020-06-05 11:50:15.211 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([title2_3_] : [VARCHAR]) - [내가 2등~]
2020-06-05 11:50:15.218 TRACE 4256 --- [    Test worker] org.hibernate.type.CollectionType        : Created collection wrapper: [com.icatapark.jpa.post.entity.Post.comments#1]
2020-06-05 11:50:15.219 TRACE 4256 --- [    Test worker] org.hibernate.type.CollectionType        : Created collection wrapper: [com.icatapark.jpa.post.entity.Post.comments#4]
Hibernate: 
    select
        comments0_.post_id as post_id3_2_0_,
        comments0_.id as id1_2_0_,
        comments0_.id as id1_2_1_,
        comments0_.comment as comment2_2_1_,
        comments0_.post_id as post_id3_2_1_ 
    from
        comment comments0_ 
    where
        comments0_.post_id=?
2020-06-05 11:50:15.228 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [1]
2020-06-05 11:50:15.239 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [2]
2020-06-05 11:50:15.240 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [첫 댓글~! ]
2020-06-05 11:50:15.240 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [1]
2020-06-05 11:50:15.242 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0_] : [BIGINT]) - [1]
2020-06-05 11:50:15.242 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0_] : [BIGINT]) - [2]
2020-06-05 11:50:15.250 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [3]
2020-06-05 11:50:15.250 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [두번째야~]
2020-06-05 11:50:15.250 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [1]
2020-06-05 11:50:15.251 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0_] : [BIGINT]) - [1]
2020-06-05 11:50:15.251 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0_] : [BIGINT]) - [3]
Hibernate: 
    select
        comments0_.post_id as post_id3_2_0_,
        comments0_.id as id1_2_0_,
        comments0_.id as id1_2_1_,
        comments0_.comment as comment2_2_1_,
        comments0_.post_id as post_id3_2_1_ 
    from
        comment comments0_ 
    where
        comments0_.post_id=?
2020-06-05 11:50:15.264 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [4]
2020-06-05 11:50:15.266 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [5]
2020-06-05 11:50:15.267 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [다음글 기대할께요.]
2020-06-05 11:50:15.267 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [4]
2020-06-05 11:50:15.268 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0_] : [BIGINT]) - [4]
2020-06-05 11:50:15.268 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0_] : [BIGINT]) - [5]
2020-06-05 11:50:15.269 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [6]
2020-06-05 11:50:15.270 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [좋아요~]
2020-06-05 11:50:15.270 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [4]
2020-06-05 11:50:15.271 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0_] : [BIGINT]) - [4]
2020-06-05 11:50:15.271 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0_] : [BIGINT]) - [6]
2020-06-05 11:50:15.271 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_1_] : [BIGINT]) - [7]
2020-06-05 11:50:15.271 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_2_1_] : [VARCHAR]) - [감사합니다.]
2020-06-05 11:50:15.272 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_1_] : [BIGINT]) - [4]
2020-06-05 11:50:15.272 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([post_id3_2_0_] : [BIGINT]) - [4]
2020-06-05 11:50:15.272 TRACE 4256 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_0_] : [BIGINT]) - [7]
[[Comment(id=2, comment=첫 댓글~! ), Comment(id=3, comment=두번째야~)], [Comment(id=7, comment=감사합니다.), Comment(id=6, comment=좋아요~), Comment(id=5, comment=다음글 기대할께요.)]]

결과를 보면 처음 select 하나만 원했을텐데 이 후로 post의 수 많큼 select * from comment where post_id=1, select * from comment where post_id=4 이렇게 2번더 실행 되었습니다. 그래서 원하는 1번의 query가 아니라 n+1번 실행되어 N+1문제라고 합니다.

 

문제는 하위 entity가 처음 select에서 가져오지 않고 실제 사용될 때  하나씩 하나씩 따로 가져오게되어 N+1 문제가 발생합니다. 실제 변수를 사용하는 시점에 JPA가 해당 변수 하나만을 가져오는 쿼리가 실행이 되는 것이죠.

JPA는 fetchType과 무관하게 그때 그때 JPQL에 맞춰 쿼리를 실행합니다. 그래서 eager든 lazy든 n+1 문제는 동일하게 일어납니다.

 

 

 

 


나는 항상 오늘 하루만 생각을 해... 가 아니라 나는 변수 하나만을 생각해... 지금 그 변수를 당장 가져와야지라고 동작한다고 보시면 됩니다.

다음 포스트는 어떻게 처리할지 알아보겠습니다.

'Programming > JPA' 카테고리의 다른 글

N + 1 문제 해결 2  (0) 2020.06.11
N + 1 문제 해결 1  (0) 2020.06.11
왜 JPA를 써야할까?  (0) 2020.06.05
JPA 기본 Annotation 정리  (7) 2019.07.04
jpa 복합키에서 auto increment  (0) 2019.06.27

+ Recent posts