JPA에서 발생하는 N+1 문제는 연관된 엔티티를 조회할 때 발생하는 성능 문제이다.
발생 원인
: 간단히 말하자면 JPA의 연관관계 처리 방식 때문에 발생한다고 할 수 있다.
즉 지연로딩(LAZY)전략으로 연관 관계가 설정된 엔티티를 조회할 때, 각각의 조회 시도에 개별적으로 추가 조회가 발생하기 때문이다.
예를 들어, 회원(Member)과 주문 (Order)이 1:N 관계일 때, 회원만을 조회한다면, 이후 각 Member의 orders를 조회할 때 추가 쿼리가 발생한다.
이러한 경우,
➡️ 첫 번째 쿼리: 회원 전체 조회 -> 1
➡️ 두 번째 쿼리: 각 회원마다 주문 조회 -> N
➡️ 총 1 + N번의 쿼리가 발생하여 문제가 됨
해결방안
1) Fetch Join을 사용한다.
JPQL에서 JOIN FETCH 구문을 사용하면, 연관된 엔티티를 한 번에 조회하는데 이건 SQL의 JOIN처럼 사용하지만, JPA가 연관 엔티티까지 한 번에 가져오게 오도록 한다. 즉, 한 번의 쿼리로 연관 객체까지 즉시 로드할 수 있다.
2) EntityGraph를 사용한다.
JPA에서 제공하는 @EntityGraph는 쿼리를 굳이 작성하지 않고도 fetch 전략을 재정의할 수 있는 방법이다.
특정 쿼리에만 Eager처럼 작동하게 변경이 가능하고 JPQL없이 선언적으로 연관 엔티티를 조회할 수도 있다. 이 방법은 다양한 쿼리에서 재사용이 가능하다는 장점이 있고 페이징 시에도 사용할 수 있다.
3) 쿼리를 최적화한다.(DTO를 직접 조회하는 방향으로)
필요한 데이터만 뽑아서 바로 DTO에 매핑하여 조회하게 하는 방법이다. N+1 문제가 애초에 발생하지 않도록 쿼리를 설계하는 것이다.
public class MemberOrderDto {
private String memberName;
private String orderName;
public MemberOrderDto(String memberName, String orderName) {
this.memberName = memberName;
this.orderName = orderName;
}
}
String jpql = "SELECT new com.example.MemberOrderDto(m.name, o.name) " +
"FROM Member m JOIN m.orders o";
List<MemberOrderDto> result = em.createQuery(jpql, MemberOrderDto.class)
.getResultList();
즉 이런식으로 MemberOrderDto를 따로 만들어서 불필요한 데이터 로딩 없이 필요한 컬럼만 조회 가능하도록 하는 것이다.
| 방법 | 특징 | 장점 | 단점 |
| Fetch Join | JPQL에서 연관 엔티티를 함께 조회 | 간단하고 직관적 | 컬렉션(@OneToMany..) 조인 시 페이징 불가 |
| EntityGraph | 애노테이션으로 Fetch Join 지정 | 선언적이고 유연함 | 설정 이해가 필요, 단순 연관관계에서는 잘 동작하나 컬렉션에서는 성능 저하 발생 가능성이 높음 |
| DTO 직접 조회 | 필요한 필드만 조회해 DTO로 반환 | 성능 효율적 | 코드 양 증가, 유지보수성이 떨어질 수 있음 |
| 방법 | 필요한 상황 |
| Fetch Join | 연관된 엔티티를 필수적으로 함께 조회햐아 하는 경우 단건 조회 또는 리스트 조회이지만 페이징이 필요 없는 경우 ex) 게시글 목록 조회 시 작성자 정보를 같이 보고 싶을 때 |
| EntityGraph | Spring Data JPA를 사용중일 경우 Fetch 전략을 선언적으로 지정하고 싶은 경우 페이징을 하면서 연관 엔티티를 조회해야 할 경우 ex) 유저 목록을 페이지 단위로 보여주면서 프로필 사진도 같이 가져와야 할 때 |
| DTO 직접 조회 | 화면에 보여줄 데이터가 명확할 경우 엔티티 전체가 아니라 몇 개의 필드만 필요할 경우 복잡한 화면 구성에서 여러 테이블의 조인이 필요할 경우 ex) 주문 목록 화면에서 주문자의 이름, 상품명, 주문일자만 보여줄 때 |
'Programming > Spring' 카테고리의 다른 글
| [Spring] Batch작업이란? (0) | 2025.11.19 |
|---|---|
| [Spring]@Cacheable, @CachePut, @CacheEvict (0) | 2025.10.20 |
| [Spring] JWT의 구조 (0) | 2025.10.01 |
| [Spring] Spring Framwork의 탄생 배경 (0) | 2025.07.16 |