백엔드/Spring

[Spring] JPA 컬렉션 조회 최적화 & OSIV

kwang2134 2024. 9. 21. 15:41
728x90
반응형
728x90

JPA 컬렉션 조회

  • 일대다 (One-to-Many), 다대다 (Many-to-Many) 관계에서 발생하는 조회로 객체 내부 필드로 컬렉션을 가질 때 발생

엔티티 조회 -> DTO 변환

  • 엔티티를 직접 조회한 뒤 내부 컬렉션과 엔티티를 DTO로 변환하여 반환
  • 단점: 지연 로딩 설정 시 프록시 초기화로 수많은 추가 쿼리 발생

엔티티 조회(fetch 조인) -> DTO 변환

  • 엔티티 조회 시 fetch 조인을 통해 미리 연관된 객체와 컬렉션을 다 가져온 뒤 DTO로 변환
  • fetch 조인을 통해 1번의 쿼리로 미리 모든 데이터를 가져와 추가 쿼리가 발생하지 않음
  • 단점: 페이징 불가능 -> 일대다로 조인하여 쿼리시 데이터가 중복되는 현상으로 원하는 페이징이 불가능(toOne 관계 제외)
  • 둘 이상의 컬렉션에 fetch 조인 불가능 -> 데이터 중복으로 row 수가 증가 

엔티티 조회(fetch 조인 + BatchSize) -> DTO 변환

  • 페이징 사용을 위해 one-to-one 관계만 fetch 조인으로 가져옴 -> toOne 관계는 1대 1 대응으로 row 수가 증가하지 않음
  • 컬렉션은 지연 로딩으로 프록시를 초기화하는데 BatchSize를 설정해 한 번에 가져오는 쿼리 수를 설정
  • BatchSize가 100이라면 프록시 객체를 한 번에 설정한 100이라는 사이즈만큼 IN 쿼리로 조회
  • 쿼리 호출 수가 1+N에서 1 + (N / BatchSize) 로 최적화 
  • fetch 조인을 사용하는 것 보다 DB 데이터 전송량이 최적화 -> 조인 시 발생하는 데이터 중복이 없음
  • 쿼리 호출 수는 증가하지만 DB 데이터 전송량은 감소기
  • 페이징 가능 -> 중복 데이터로 row가 증가하지 않음

Hibernate 6.2 이후 array_contains

  • Hibernate 6.2 이후 WHERE IN 대신 array_contains 사용
  • 기존의 WHERE item.id IN (?,?,?,?) 대신 array_contains(?, item.id) 형태로 사용 -> 배열 내 데이터가 오른쪽에 있다면 참이 됨
  • :itemId와 같이 파라미터 바인딩 시 컬렉션이 아닌 배열 형태의 파라미터를 사용
  • 컬렉션이 List<Long> itemIds 형태라면 itemIds.toArray(new Long []{}) 형태로 배열로 변환한 인자를 사용
  • WHERE IN 절에 비해 성능 최적화 -> 데이터베이스는 SQL 문 자체를 내부에 캐싱(실행 결과 X)
  • WHERE IN 절 사용시 바인딩되는 데이터의 수가 변경될 때마다 쿼리가 동적으로 변함
  • 1개: WHERE IN (?), 2개: WHERE IN (?,?)...
  • array_contains 사용 시 배열 자체를 바인딩해 바인딩하는 데이터는 달라져도 구문은 바뀌지 않음 -> 캐싱된 구문을 사용 가능
  • 표준 JPQL 문법이 아닌 Hibernate 확장 기능으로 모든 데이터베이스에서 지원하지 않음

JPA에서 DTO 직접 조회

  • toOne 관계는 join으로 한 번에 DTO를 사용해 조회한
  • 1:N 관계인 컬렉션은 별도로 DTO를 사용해 조회한 뒤 조립
  • 루트 쿼리 1번 + 컬렉션 N 번의 쿼리 발생

JPA에서 DTO 직접 조회 최적화

  • toOne 관계는 join으로 한 번에 DTO를 사용해 조회
  • 1:N 관계인 컬렉션은 toOne 관계 조인으로 얻은 식별자를 통해 한 번에 다 조회한 뒤 Map으로 메모리에 매핑 한 뒤 메모리에 저장된 데이터를 사용해 조립
  • 루트 쿼리 1번 + 컬렉션 쿼리 1번으로 2번의 쿼리로 해결

OSIV (Open Session In View)

  • 웹 애플리케이션에서 데이터베이스 세션을 HTTP 요청의 생명 주기 동안 열어두는 방식

동작 방식

  1. 세션 열기
    • 클라이언트의 요청이 서버에 도착하면, OSIV 패턴에 의해 데이터베이스 세션이 열림
    • 세션은 요청이 처리되는 동안 유지
  2. 비즈니스 로직 처리
    • 요청을 처리하는 동안 필요한 엔티티를 조회하고 비즈니스 로직을 수행
    • 지연 로딩이 필요한 경우 아직 로드되지 않은 엔티티의 속성을 사용할 수 있음
  3. 응답 생성
    • 클라이언트에 대한 응답을 생성하기 전 세션이 여전히 열려 있기 때문에 필요한 데이터가 지연 로딩을 통해 자동으로 로드
  4. 세션 닫기
    • 요청이 완료되면 세션이 닫힘
    • 모든 변경 사항이 데이터베이스에 반영

장점

  • 지연 로딩을 통해 필요할 때만 데이터를 로드할 수 있음

단점

  • 세션이 열려 있는 동안 많은 쿼리가 발생 가능해 성능 저하 가능
  • 트랜잭션의 범위가 넓어져 관리가 복잡해 짐
  • 세션이 열려 있는 동안 데이터가 메모리에 유지되어 메모리 소비가 증가 -> 커넥션이 반납되지 않아 장애 발생
  • 영속성 컨텍스트가 뷰 렌더링 시점까지 열려있어 의도치 않은 데이터 변경이 발생 가능

실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화 강의 | 김영한 - 인프런 강의 내용 참고

 

728x90

'백엔드 > Spring' 카테고리의 다른 글

[Spring] 쿼리 메서드  (0) 2024.09.23
[Spring] Spring Data JPA  (2) 2024.09.22
[Spring] JPA 지연 로딩 & 조회 성능 최적화  (0) 2024.09.20
[Spring] API  (1) 2024.09.19
[Spring] JPA - JPQL(Java Persistence Query Language)  (1) 2024.09.13