Zayson
A to Zayson!
Zayson
전체 방문자
오늘
어제
  • 분류 전체보기 (132)
    • Computer Science (20)
      • Network (4)
      • DB (12)
      • OS (4)
    • Algorithm (32)
      • 완전탐색(Brute-Force) (3)
      • 그리디(Greedy) (6)
      • 투포인터(Two-Pointer) (1)
      • 그래프(Graph) (5)
      • BFS & DFS (9)
      • 구현, 시뮬레이션(Implementation) (5)
      • 다이나믹 프로그래밍(DP) (3)
    • Backend (51)
      • Spring Boot (19)
      • JPA (16)
      • Kafka (2)
      • Java (13)
      • Kotlin (1)
    • DevOps (1)
      • Jenkins (5)
      • Oracle Cloud Infrastructure (1)
      • Kubernetes & Docker (1)
    • Trouble Shooting (3)
      • JPA (1)
      • Spring Boot (2)
    • 회고 (5)
      • 엔빵 프로젝트 포스트 로드맵 (1)
      • 2022년 (4)
    • Kafka (7)
      • Kafka (5)
      • Kafka Connect (2)
    • 기술 서적 (6)
      • 데이터 중심 애플리케이션 설계 (3)
      • 개발자가 반드시 정복해야할 객체 지향과 디자인 패턴 (2)
      • 가상 면접 사례로 배우는 대규모 시스템 설계 기초 (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

인기 글

태그

  • 완전탐색
  • dfs
  • Java
  • 백준
  • Kafka Connect
  • 프로그래머스
  • JPA
  • 엔빵프로젝트
  • 그리디
  • Backend
  • 관계형 데이터베이스 실전 입문
  • CS
  • 라이브스터디
  • spring boot
  • 구현
  • kafka
  • BFS
  • Computer science
  • 나 혼자 스프링부트!
  • SpringBoot

최근 글

티스토리

hELLO · Designed By 정상우.
Zayson

A to Zayson!

[API 개발 고급] 지연 로딩과 조회 성능 최적화
Backend/Spring Boot

[API 개발 고급] 지연 로딩과 조회 성능 최적화

2022. 7. 7. 12:28

“김영한 강사님의 JPA 활용편 2 - API 개발과 성능 최적화"를 듣고 간단하게 정리하기”

 

  • 양방향 연관관계에서 직접 데이터를 반환하는 경우 서로가 서로를 참조하기 때문에 무한루프가 발생한다.
Order -> Member -> Order -> Member ... 무한루프발생!

 

  • 양방향 연관관계가 매핑된 엔티티 한 쪽에는 @JsonIgnore를 사용한다.
  • 지연로딩으로 설정된 값은 DB에서 가져오지 않고 가짜 Proxy 객체를 생성하고 실제 객체에 접근하고자 할때 그 때 프록시가 쿼리를 날려서 DB에서 값을 가져온다.
  • Hibernate5Module와 @JsonIgnore를 이용해 엔티티를 넘긴 경우 무한루프 없이 데이터를 조회해 오는 것이 가능하다.
  • 지연 로딩을 피하기 위해서 즉시 로딩 (EAGER)로 변경하지 않는다. 연관관계가 필요없는 경우에도 데이터를 조회하기 때문에 성능 튜닝이 어려워진다.
[
    {
        "id": 4,
        "member": null,     // 지연 로딩
        "orderItems": null,
        "delivery": null,
        "orderDate": "2022-07-06T10:44:43.457581",
        "status": "ORDER",
        "totalPrice": 50000
    },
    {
        "id": 11,
        "member": null,
        "orderItems": null,
        "delivery": null,
        "orderDate": "2022-07-06T10:44:43.511754",
        "status": "ORDER",
        "totalPrice": 220000
    }
]

 

  • 하지만, 엔티티를 직접 프레젠테이션 계층에 넘기는 것은 하면 안된다!!! (V1의 문제점)
  • DTO로 변환해서 화면으로 응답한다. (V1의 해결책)
@GetMapping("/api/v2/simple-orders")
public List<SimpleOrderDto> orderV2() {
		// Order SQL 1번 실행 -> 결과 주문수 2개
    // N + 1문제라고 한다!! 첫번째 쿼리의 결과로 N번의 쿼리가 추가 실행된다.
    // N + 1 : 주문 1 + 회원 N + 배송 N
    List<Order> orders = orderRepository.findAllByString(new OrderSearch());

		// Member, Delivery 각 두번 씩 조회
    return orders.stream()
            .map(order -> new SimpleOrderDto(order))
            .collect(Collectors.toList());
}

@Data
static class SimpleOrderDto {
    private Long orderId;
    private String name;
    private LocalDateTime orderDate;
    private OrderStatus orderStatus;
    private Address address;

    public SimpleOrderDto(Order order) {
        orderId = order.getId();
        name = order.getMember().getName();  // LAZY가 초기화 : MemberId를 가지고 영속성 컨텍스트를 조회한 후 없는 경우 DB에 쿼리를 날린다.
        orderDate = order.getOrderDate();
        orderStatus = order.getStatus();
        address = order.getDelivery().getAddress();
    }
} 

 

  • DTO로 반환하는 경우 엔티티의 컬럼이 바뀌거나 하더라도 컴파일 단계에서 에러 체크가 가능하다.
  • V1, V2 모두 지연 로딩으로 인한 데이터베이스 쿼리가 너무 많이 호출되는 단점이 있다. Member, Order, Delivery 3개의 엔티티를 한번에 호출한다.
  • 영속성 컨텍스트가 주문 조회 SQL을 먼저 날린다. 쿼리는 한개만 날아간다.
  • 쿼리를 통해 주문이 2개가 조회되면, 각 주문 당 지연로딩에 따라 Member의 Name, Delivery의 Address 쿼리가 1번씩 추가 실행된다.
  • 즉, 주문 쿼리 1번에 회원, 배송 테이블의 쿼리가 N번 추가 조회되는 N + 1문제가 발생한다.
  • 지연 로딩은 영속성 컨텍스트를 조회하기 때문에 이미 조회된 경우 쿼리를 생략한다.
  • 따라서, 성능 향상을 위해서 fetch join을 이용한다. (V1, V2 : 지연 로딩으로 인한 N + 1의 해결책)

 

public List<Order> findAllWithMemberDelivery() {
    // Fetch Join을 이용 -> Order를 가져올 때 객체 그래프를 탐색해서 Member와 Delivery를 한번에 가져온다.
    return em.createQuery("select o from Order o" +
            " join fetch o.member m" +
            " join fetch o.delivery d", Order.class
    ).getResultList();
}
  • Fetch join을 이용해 주문(SQL 1번)을 가져올 때 객체 그래프를 탐색해 지연로딩되는 데이터를 한번에 가져온다. (V3)

모든 컬럼이 조회된다.

  • 기존 N+1번 돌던 쿼리가 단 1번의 쿼리로 모든 데이터를 조회할 수 있다.
  • V3의 약간의 단점은 먼저 엔티티로 조회한 후 엔티티를 DTO에 매핑하는 단계가 있다.
  • JPA를 이용해 데이터베이스에서 DTO로 바로 꺼냄으로써 성능을 더욱 최적화 시키는 것이 가능하다.
public List<OrderSimpleQueryDto> findOrderDtos() {
    return em.createQuery("select new jpabook.jpashop.repository.OrderSimpleQueryDto(o.id, m.name, o.orderDate, o.status, d.address)" +
            " from Order o" +
            " join o.member m" +
            " join o.delivery d", OrderSimpleQueryDto.class)
            .getResultList();
}

@Data
public class OrderSimpleQueryDto {
    private Long orderId;
    private String name;
    private LocalDateTime orderDate;
    private OrderStatus orderStatus;
    private Address address;

    public OrderSimpleQueryDto(Long orderId, String name, LocalDateTime orderDate, OrderStatus orderStatus, Address address) {
        this.orderId = orderId;
        this.name = name;
        this.orderDate = orderDate;
        this.orderStatus = orderStatus;
        this.address = address;
    }
}

 

  • JPQL을 이용해 new를 이용해 DTO의 데이터에 값을 매핑해 즉시 반환한다.
  • V3에서는 엔티티의 모든 컬럼을 조회하기 때문에 중복되는 컬럼도 함께 조회되었지만, DTO를 직접 매핑해준 경우 원하는 데이터만 DB에서 조회해 오기 때문에 네트웍 용량을 최적화할 수 있다.

원하는 데이터만 조회한다.

  • V4는 JPQL을 이용해서 성능을 최적화 시키는 것은 가능하지만, 해당 DTO를 이용하는 경우를 제외하면 재사용하는 것이 불가능하다는 단점이 있다.
  • 그에 비해 V3은 엔티티 자체를 컨트롤 하는 것이기 때문에 다른 API에서도 원하는 DTO로 변환하여 사용할 수 있기 때문에 재사용성이 좋다.

 

정리

  • Repository는 화면과 직접 연관되게 성능 최적화를 해야하는 (DTO로 직접 반환) 부분과 엔티티를 조회하는 Repository를 구분해주는 것을 권장한다.
  • 엔티티를 DTO로 변환하는 방법을 우선 선택한 후 Fetch Join을 이용해 성능 최적화한다.
  • DTO로 직접 조회하는 방법은 Fetch Join을 사용해도 성능 이슈가 있는 경우에 사용한다.
  • DTO로 직접 조회하는 방법도 성능 이슈가 있는 경우 JPA의 네이티브 SQL이나 스프링 JDBC Template을 이용해 SQL을 직접 사용한다.
반응형
저작자표시 비영리 변경금지 (새창열림)

'Backend > Spring Boot' 카테고리의 다른 글

Repository 단위 테스트  (0) 2022.07.15
[API 고급] 컬렉션 조회 최적화  (0) 2022.07.12
API 개발 기본 간단 정리  (0) 2022.07.07
도메인 개발 팁 간단 정리  (0) 2022.07.05
RestTemplate  (0) 2022.07.01
    'Backend/Spring Boot' 카테고리의 다른 글
    • Repository 단위 테스트
    • [API 고급] 컬렉션 조회 최적화
    • API 개발 기본 간단 정리
    • 도메인 개발 팁 간단 정리
    Zayson
    Zayson
    공부한 내용을 정리하는 공간

    티스토리툴바