문제 분석
1. N+1 문제 (주요 원인)
// 기존 쿼리
@Query("SELECT ap FROM AirPurchase ap " +
"LEFT JOIN FETCH ap.airPassengers passengers " +
"LEFT JOIN FETCH passengers.specialRequests " + // ← 이 부분이 문제
"WHERE ap.productPurchaseId = :id")
N+1 문제 발생 과정:
- AirPurchase 1개 조회
- AirPassengers N개 조회 (JOIN FETCH)
- 각 AirPassenger의 specialRequests M개씩 조회 (JOIN FETCH)
결과: 1 + N + (N × M) = 1 + N + NM 개의 쿼리 실행
2. LAZY 문제 (부차적 원인)
@ManyToMany(fetch = FetchType.LAZY)
private Set<SpecialRequest> specialRequests = new HashSet<>();
LAZY 문제 발생:
- specialRequests가 LAZY로 설정되어 있어서
해결 방법별 효과
방법 1: Set 사용 (현재 적용)
// AirPurchase에서
private Set<AirPassenger> airPassengers = new HashSet<>();
효과: 중복 제거만 해결, N+1 문제는 여전히 존재
방법 2: 쿼리 분리 (더 근본적 해결)
// 1단계: AirPurchase + AirPassengers만 조회
@Query("SELECT ap FROM AirPurchase ap " +
"LEFT JOIN FETCH ap.airPassengers passengers " +
"WHERE ap.productPurchaseId = :id")
// 2단계: 필요시 SpecialRequests 별도 조회
@Query("SELECT sr FROM SpecialRequest sr " +
"JOIN sr.airPassengers ap " +
"WHERE ap.airPurchase.productPurchaseId = :purchaseId")
방법 3: 배치 크기 설정
// application.properties
spring.jpa.properties.hibernate.jdbc.batch_size=20
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true
현재 상황에서의 문제 유형
문제원인해결 방법
| 중복 표시 |
N+1 조인으로 인한 중복 |
✅ Set 사용으로 해결 |
| 성능 저하 |
N+1 문제 |
🔄 쿼리 분리 필요 |
| 지연 로딩 |
LAZY 설정 |
�� EAGER 또는 별도 조회 |
권장 해결책
현재는 중복 문제만 해결했으므로, 성능 최적화를 위해 쿼리를 분리하는 것을 추천합니다:
// AirPurchaseRepository.java
@Query("SELECT ap FROM AirPurchase ap " +
"LEFT JOIN FETCH ap.airPassengers passengers " +
"WHERE ap.productPurchaseId = :id")
Optional<AirPurchase> findByIdWithPassengers(@Param("id") Long id);
// SpecialRequest는 필요시 별도 조회
@Query("SELECT DISTINCT sr FROM SpecialRequest sr " +
"JOIN sr.airPassengers ap " +
"WHERE ap.airPurchase.productPurchaseId = :purchaseId")
List<SpecialRequest> findSpecialRequestsByPurchaseId(@Param("purchaseId") Long purchaseId);
결론: 현재 해결한 것은 N+1 문제의 부작용(중복)이고, 근본적인 N+1 문제는 쿼리 분리로 해결해야 합니다.