🎯 1. 왜 쿼리를 최소화해야 하는가?
- 쿼리 수 = DB 접근 횟수
- DB 접근은 가장 비용이 큰 작업 중 하나 → 응답 지연 발생
- 예: 불필요한 연관 쿼리로 인해 4~5초까지 지연되기도 함
- 즉, 쿼리를 줄이면 성능 최적화에 직결
팁: N+1 문제는 반드시 점검해야 할 핵심 성능 이슈! 연관 엔티티가 많을수록 효과적으로 해결해야 함.
🧱 2. Entity 대신 DTO를 사용하는 이유
이유설명
스택 오버플로우 위험 | 순환 참조 구조에서 직렬화 시 무한 루프 발생 가능 |
불필요한 데이터 포함 | 모든 필드를 노출함으로써 응답 페이로드가 커짐 |
API 확장에 제약 | Entity는 도메인 모델이므로 API 요구사항 변화에 유연하지 않음 |
팁: API 계층에서는 Entity를 직접 노출하지 않고 DTO로 필요한 필드만 추리는 것이 안정적!
🔁 3. Fetch Join
- SQL 조인을 통해 연관된 엔티티를 한 번의 쿼리로 함께 조회
- 단순한 연관 로딩 처리에 적합
- 사용 예: select o from Order o join fetch o.member
- 단점:
- 특정 컬럼 조회 어려움 → ex. member의 이름만 조회할 경우
- 페이징 불가 (OneToMany 연관 시)
팁: 단순 연관 데이터 + 성능이 중요한 상황에서는 Fetch Join이 강력!
🧭 4. EntityGraph
- @EntityGraph 어노테이션으로 Fetch 전략을 명시
- 복잡한 JPQL 없이도 특정 연관 관계를 즉시 로딩 가능( 복잡한 연관 관계X)
- orlist.order.member.id는 조회X
- Fetch Join보다 확장성 좋음
- 단점:
- 복잡한 조건 쿼리에는 부적합
- 페이징은 가능하지만 조회 범위 조절이 어려움
팁: Repository에서 자주 사용하는 패턴이라면 Fetch Join보다 유지보수 용이!
📦 5. DTO 직접 조회 (JPQL / QueryDSL)
- 쿼리에서 필요한 필드만 추출하여 DTO로 직접 매핑
- 성능 좋고, 서비스 계층의 로직 단순화
- 사용 예:SELECT new com.example.OrderDto(o.id, m.name)
FROM Order o JOIN o.member m
- 단점:
- 재사용 어려움
- 확장성 낮음 (필드 하나만 바뀌어도 DTO/쿼리 수정 필요)
팁: 화면에 딱 맞는 조회가 필요하고 재사용 가능성이 낮다면 적극 활용!
📚 6. BatchSize
- Hibernate 설정으로 LAZY 로딩 시 in 쿼리로 일괄 조회 가능
- @BatchSize(size = 100) or spring.jpa.properties.hibernate.default_batch_fetch_size=100
- 활용 시:
- 1:N 관계 로딩 최적화
- N개의 Select → 1개의 in절 쿼리로 축소
팁: LAZY + 반복 접근이 예상된다면 꼭 설정해두자! 아주 큰 성능 향상 유도 가능.
🧩 보너스: DISTINCT
- Fetch Join 시 발생하는 중복 데이터 제거에 사용
- 하지만 메모리 상에서 중복을 제거하는 방식이기 때문에, 잘못 사용하면 성능 악화 가능성 있음
팁: DISTINCT는 필요할 때만, 신중히 사용하세요. 의도하지 않은 중복 제거에 주의!
✅ 오늘의 키워드 요약
기술주요 포인트페이징 지원
Fetch Join | 한 번의 쿼리로 연관 데이터 로딩 | ❌ (특히 1:N) |
EntityGraph | 어노테이션 기반 로딩 설정 | ⭕ |
DTO 직접 조회 | 필드만 뽑아오는 쿼리 작성 | ⭕ |
BatchSize | 지연 로딩 시 성능 향상 | ⭕ |
DISTINCT | 중복 제거 | ⚠️ (신중히 사용) |