본문 바로가기

카테고리 없음

📘 TIL: JPA 성능 최적화 전략 정리 (쿼리 최소화, DTO 사용 등)

🎯 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 중복 제거 ⚠️ (신중히 사용)