중복 문제
Post와 Comment가 일대다(1:N) 관계이고, Post에서 @OneToMany로 Comment를 참조하고 있다.
@Entity
@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED)
@Getter
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "post")
private List<Comment> comments;
}
@Entity
@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED)
@Getter
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
다음과 같이 JOIN FETCH을 이용해서 Post를 불러올 경우 문제가 발생한다.
@Query("select p from Post p join fetch p.comments")
List<Post> findPostsWithComments();
OneToMany 관계에서 FETCH JOIN을 사용할 경우 데이터 중복이 발생한다. 이는 JPA의 특성 때문인데, JPA는 FETCH JOIN을 사용하면 카테시안 곱을 사용해서 데이터를 가져오기 때문이다.
예를 들어, Post인 P1이 있고, P1에 대한 Comment인 C1, C2, C3가 있다고 하자. 이 때, P1에 대한 Comment를 가져오는데 Fetch Join을 사용하면 P1이 3개가 나온다.
이를 해결하기 위해서는 distinct를 사용해 중복을 제거해주면 된다.
@Query("select distinct p from Post p join fetch p.comments")
List<Post> findAllPostsWithComments();
참고로, Spring Boot 3.0부터는 Hibernate 6.1을 사용하는데 Hibernate 6.0부터는 distinct를 사용하지 않아도 자동으로 적용해준다.
Hibernate 공식문서를 확인해보자.
페이지네이션 OOM 문제
OneToMany 관계에서 Fetch Join과 Pagination API를 동시에 사용할 경우, OutOfMemoryError가 발생할 수 있습니다.
이는 Fetch Join으로 인해 중복된 데이터가 메모리에 로드되어 발생하는 문제입니다. 예를 들어, 하나의 부모 엔티티에 여러 자식 엔티티가 존재할 때, 부모 엔티티의 데이터가 자식 엔티티의 개수만큼 반복되어 메모리 사용량이 급증하게 됩니다.
이 문제를 해결하기 위해 Fetch Join 대신 default batch fetch size를 활용하거나 @BatchSize 어노테이션을 사용하는 방법이 있습니다. 이러한 접근 방식은 한 번의 요청으로 배치 사이즈만큼의 데이터를 가져오도록 하여 메모리 사용을 최적화합니다. 이로 인해 중복 데이터 로드를 피하고, 성능과 메모리 관리 측면에서 효율성을 높일 수 있습니다.
FETCH JOIN 부분을 지우고, application.yml에 default_batch_fetch_size를 적용해서 사용할 수 있다.
// application.yml
spring:
jpa:
properties:
hibernate:
default_batch_fetch_size: 30
'웹 백엔드' 카테고리의 다른 글
무중단 배포(블루/그린 배포)로 서비스 중단 없이 배포하기 (0) | 2025.01.09 |
---|---|
Git Submodule(서브모듈)을 통해서 Spring 설정(yml) 관리하기 (0) | 2025.01.05 |
Flyway를 통한 데이터베이스 마이그레이션을 알아보자 (1) | 2025.01.03 |
@JsonTypeInfo와 @JsonSubTypes를 활용하여 요청 데이터에 다형성을 적용해 보자 (1) | 2024.12.25 |
Spring JPA를 사용할 때 findByXxxId의 문제점을 알아보자 (0) | 2024.12.23 |