상세 컨텐츠

본문 제목

[SPRING] JPA 활용2 | API 개발과 성능 최적화(1)

JAVA/기본 & 강의복습

by ranlan 2021. 4. 20. 00:56

본문

728x90

[Inflearn] 실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화

 

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

스프링 부트와 JPA를 활용해서 API를 개발합니다. 그리고 JPA 극한의 성능 최적화 방법을 학습할 수 있습니다., 본 강의는 자바 백엔드 개발의 실전 코스에 있는 활용2 강의 입니다. 스프링 부트와 J

www.inflearn.com


 

 

API 설계 기본

화면 컨트롤러와 API를 위한 컨트롤러를 패키지를 나눠 분리

VIEW 컨트롤러는 @Controller, API 컨트롤러는 @RestController

 

RequestBody와 ResponseBody에 엔티티를 직접 담는 것은 좋지 않다.

엔티티에 프레젠테이션을 위한 로직이 추가되어 API 스펙에도 혼란을 줄 수 있고 유지보수가 어려워진다. 따라서 별도의 Request 객체나 DTO를 만들어 사용하는 것이 좋다.

 

 

V1. 엔티티 직접 노출

OrderApiController

List<Order> all = orderRepository.findAll();

return all;

 

문제 1) 순환참조 문제 2021.04.14 - [study/spring boot & jpa] - [ERROR] 순환참조 문제

 

[ERROR] 순환참조 문제

문제 차근차근 JPA를 사용하여 로직을 만들고 잘 나오는지 테스트를 하고 있었다. 그런데 갑자기ㅠㅠ 아주 난리가 났다.. 쿼리를 살펴보니 계속해서 같은 쿼리가 반복되고 포스트맨으로 응답 결

juran-devblog.tistory.com

문제 2) 연관관계가 모두 지연로딩으로 설정되어 있어 Member와 Address는 프록시 객체

jackson 라이브러리는 기본적으로 이 프록시 객체를 json으로 어떻게 생성해야 몰라 예외가 발생하게 됨

 

해결 2.1) Hibernate5Module를 빈으로 등록하여 해결

buil.gradle

// Hibernate5Module
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate5'

빈으로 등록

@Bean
Hibernate5Module hibernate5Module() {

    Hibernate5Module hibernate5Module = new Hibernate5Module();

    hibernate5Module.configure(Hibernate5Module.Feature.FORCE_LAZY_LOADING, true);
    return hibernate5Module;
}

기본적으로 초기화된 프록시 객체만 노출되고 초기화되지 않은 프록시 객체는 노출되지 않음

 

 해결 2.2) for문을 통해 LAZY 강제 초기화

List<Order> all = orderRepository.findAll();

// Lazy 강제 초기화
for (Order order : all) {
    order.getMember().getName();
    order.getDelivery().getAddress(); 
}

 

문제 3) 엔티티 직접 노출에 의한 문제 - api 스펙 관련 이슈와 성능 문제

 

 

V2. 엔티티를 DTO로 변환

OrderApiController

List<Order> orders = orderRepository.findAll();

// entity -> DTO
List<SimpleOrderDto> result = orders.stream()
    .map(o -> new SimpleOrderDto(o))
    .collect(toList());

return result;

N + 1 문제로 인한 성능 저하

지연로딩으로 인해 연관관계에 따라 예상치 못한 쿼리가 추가로 발생하는 문제

예) Order 조회 1번 + Order 조회 결과마다 Member 조회 N번 + Order 조회 결과마다 Address 조회 N번

 

 

V3. 페치 조인 최적화

OrderRepository

public List<Order> findAllV3() {
    return em.createQuery(
                "select o from Order o" +
                " join fetch o.member m" +
                " join fetch o.delivery d", Order.class
    ).getResultList();
}

페치 조인을 이용하여 쿼리 하나로 해결

# 2021-04-21 00:38:05.299 DEBUG 24340 --- [nio-8080-exec-6] org.hibernate.SQL                        : 
    select
        order0_.order_id as order_id1_6_0_,
        member1_.member_id as member_i1_4_1_,
        delivery2_.delivery_id as delivery1_2_2_,
        order0_.delivery_id as delivery4_6_0_,
        order0_.member_id as member_i5_6_0_,
        order0_.order_date as order_da2_6_0_,
        order0_.status as status3_6_0_,
        member1_.city as city2_4_1_,
        member1_.street as street3_4_1_,
        member1_.zipcode as zipcode4_4_1_,
        member1_.name as name5_4_1_,
        delivery2_.city as city2_2_2_,
        delivery2_.street as street3_2_2_,
        delivery2_.zipcode as zipcode4_2_2_,
        delivery2_.status as status5_2_2_ 
    from
        orders order0_ 
    inner join
        member member1_ 
            on order0_.member_id=member1_.member_id 
    inner join
        delivery delivery2_ 
            on order0_.delivery_id=delivery2_.delivery_id

select절에 모든 컬럼이 포함됨 → 중복 데이터가 많아 데이터 전송 시간과 용량 증가

 

 

V4. JPA에서 DTO로 바로 조회

OrderRepository

public List<OrderSimpleQueryDto> findAllV4() {
    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();
}

select절에 원하는 컬럼만 포함하여 성능이 좀 더 최적화됨 (QueryDSL 이용시 더 간편하게 작성할 수 있음)

# 2021-04-21 00:42:16.662 DEBUG 24340 --- [nio-8080-exec-9] org.hibernate.SQL                        : 
select
        order0_.order_id as col_0_0_,
        member1_.name as col_1_0_,
        order0_.order_date as col_2_0_,
        order0_.status as col_3_0_,
        delivery2_.city as col_4_0_,
        delivery2_.street as col_4_1_,
        delivery2_.zipcode as col_4_2_ 
    from
        orders order0_ 
    inner join
        member member1_ 
            on order0_.member_id=member1_.member_id 
    inner join
        delivery delivery2_ 
            on order0_.delivery_id=delivery2_.delivery_id

api 스펙에 맞춘 메서드로써 재사용성이 떨어짐

v3과 v4의 성능이 크게 차이나지 않음

728x90

관련글 더보기

댓글 영역