221102 TIL JPA n + 1문제란

모델 설계 관련 이야기를 하던 중 어제 객체 연관 관계를 설정해줄 때 사용한 @ManyToOne, @OneToMany어노테이션을 사용할 때 문제점으로 n + 1 문제를 말해주셨다. n + 1 문제는 무조건 알고 있는 게 좋다고 하셨고 JPA를 사용한다면 알고 있어야 한다고 말하셨다/

내가 원치 않아도 n + 1 문제를 만날 수 있고 이는 성능에 영향을 줄 수 있기 때문에 n + 1문제는 알고있자

N + 1 문제란?

n + 1문제는 엔티티를 조회할 때 1번 조회해야 할 것을 연관 관계가 설정된 N개 종류의 데이터 각각을 추가로 조회하게 돼서 총 N+1번 조회를 하게 되는 문제이다.

 

예시로 게시글과 댓글을 @ManyToOne, @OneToMany를 이용해 연관 관계를 설정한 Entity 코드이다. 

Post.java

@Entity
public class Post {
  @GeneratedValue
  @Id
  @Column(name = "post_id")
  private Long id;

  @OneToMany(mappedBy = "post")
  private List<Comment> comments = new ArrayList<>();
  
  public Post() {
  }
  
  public User(Long id, List<Comment> comments) {
        this.comments = comments;
    }
  
  public List<Comment> getComments() {
    return comments;
  }
 }

 

Comment.java

@Entity
public class Comment {
  @Id 
  @GeneratedValue
  @Column(name = "comment_id")
  private Long id;

  private String content;
  
  @ManyToOne
  @JoinColumn(name = "post_id")
  private Post post;
  
  public Comment() {
  }
  
  public Comment(Long id, String content, Post post) {
  	this.id = id;
    this.content = content;
    this.post = post;
  }
  
  public Post getPost() {
    return post;
  }
}

한 개의 게시글은 여러 개의 댓글을 가질 수 있어 @OneToMany로 댓글(Comment)에 게시글(Post) 엔티티가 참조되고 있다.

위와 같은 관계일 때 댓글을 조회하게 되면 댓글뿐만 아니라 댓글이 참조하고 있는 게시글도 같이 조회하게 되면서 n + 1 문제가 발생한다.

 

해결 방법

Fetch Join

JPQL을 사용하여 DB에서 데이터를 가져올 때 처음부터 연관된 데이터까지 같이 가져오게 하는 방법 

※ JPQL(Java Persistence Query Language)은 객체지향 쿼리 언어이다. 따라서 테이블을 대상으로 쿼리를 날리는 것이 아니라 엔티티 객체를 대상으로 쿼리를 날리는 언어이다.

별도의 메소드를 만들어줘야 하며 @Query 어노테이션을 사용해서 "join fetch 엔티티.연관관계_엔티티" 구문을 만들어 주면 된다.

 

아무튼 n + 1 문제 떄문에 @ManyToOne, @OneToMany로 서로 참조하게 하는 방식보다는 한 객체는 객체에 맞는 필드만 갖고 참조해야 할 객체들의 아이디 값만 갖게 해서 그 아이디로 필요한 값들을 찾아 dto로 변환시켜 객체를 전달해주는 방법이 있다고 말해주셨다.

 

하지만 나는 이미 어제 @ManyToOne, @OneToMany 어노테이션을 사용해서 객체들끼리 연관관계를 설정하고 진행했던 상태라 다시 지우고 하기에는 너무 많은 수정(테스트 코드 까지)이 발생할 것 같아 그냥 진행하려 했었는데 노아님이 엔티티에 컬렉션을 사용하지 않고 진행해보라고 말하셔서 불가피하게 수정을 하게 되었다.


전날에 객체 연관관계 매핑을 위해 꽤 많은 시간을 투자했었지만 jpa 공부했다 생각하고 dto를 이용하는 방법으로 리팩터링을 했다.
변경된 데스크들을 통과시키고 구현한 기능들을 원상태로 돌리기까지 3시간 정도 걸렸다.. 객체 하나를 바꾸면 관련된 기능들 모두 바꿔줘야 하는데 모든 객체를 바꿔야 하니 손봐야 하는 게 한 두 개가 아니었다..
오늘 목표가 댓글 대댓글 구현이었는데 오전에  @ManyToOne, @OneToMany로 댓글 대댓글을 불러오는 것까지 구현했었다. 내일은 댓글 대댓글 작성까지 구현하고 실시간 채팅 기능을 알아보자