본문 바로가기

Spring boot

[spring boot] API 성능 최적화 (+ DTO 장점)

API 파라매터로 엔티티를 바로 받기
장점)새로운 class안만들어도되니까 덜 귀찮

--------------------

API 파라매터로 DTO 만들어서 받기

장점) 엔티티 필드가 변경돼도 API스펙이 변경될 리 없음(컴파일오류떠서 고쳐주면됨)

장점) 어느 필드는 NotEmpty인데 다른 api에서는 이 필드가 NotEmpty가 아닐 수 있음. 

장점) 이 API에서는 어느어느 필드가 들어올지 한눈에 알 수 있음.

 

API는 데이터 요청이 들어오는 것과 나가는것에서 절대  Entity를 사용하지 않는다. (Entity를 외부에 노출시키지 않는다.)  ==> DTO를 사용하자

 

1. API 개발 고급 - 간단한 조회

쿼리 방식 선택 권장 순서 

1. 엔티티를 DTO로 변환 (V2)

2. 필요하면 fetch join으로 성능 최적화 (V3)

--------------- 대부분 여기서 해결됨

3. DTO로 직접 조회하는 방법 사용 (V4)

4. 최후의 방법..! JPA가 제공하는 네이티브 SQL이나 스프링 JDBC template를 사용해서 SQL을 직접 사용

 

 

2. API 개발 고급 - 컬렉션 조회 최적화

※ 여기서 엔티티 조회방식이란?

더보기

(아래는 참고)아래 부분에 1. 엔티티 조회와 2. 엔티티를 DTO로 변환 방식이 있는데

이 중 엔티티 조회를 말하며 SQL 결과를 엔티티로 받을지, 바로 DTO로 변환하여 가져올지의 차이이다.

 

1. 엔티티 조회 방식으로 우선 접근

      1. 페이징 필요 x : 페치조인으로 쿼리 수를 최적화 (V3)

      2. 페이징 필요 o : 바로 아래 1, 2번 실행 (V3.1)

          1. 먼저 ToOne(OneToOne, ManyToOne) 관계를 모두 페치조인 한다.

               - ToOne 관계는 row수를 증가시키지 않으므로 페이징 쿼리에 영향을 주지 않음.

          2. 컬렉션은 따로 hibernate.default_batch_fetch_size , @BatchSize 로 최적화 

               - 옵션을 사용하면 컬렉션이나, 프록시 객체를 한꺼번에 설정한 size 만큼 IN 쿼리로 조회한다.

2. 엔티티 조회 방식으로 해결이 안되면 DTO 조회 방식 사용 (보통 V5)

3. DTO 조회 방식으로 해결이 안되면 NativeSQL or 스프링 JdbcTemplate

 

 

 

 

 

 

 

(아래는 참고)

 

1. 엔티티 조회

    1. (V3) 페이징 필요 없을 때  ( 컬렉션 조회 SQL 1번)

       1. DTO의 어느 곳에도 컬렉션이 없게 만들기

           - DTO  안의 컬렉션이 Entity면 외부에 노출되기 때문에 DTO 속 컬렉션도 DTO로 만들어주기

       2. fetch join과 distinct 사용 (컬렉션 페치 조인은 1개의 컬렉션과만 사용할 수 있다.)

           - distinct : DB에서 distinct가 포함된 쿼리 결과로 완전히 똑같은 데이터면 DB에서 걸리지지만 완전히 같지 않고 일부만 다르면 jpa가 데이터를 가져올 때 from절에 있는 데이터(root 엔티티)의 id가 같을 경우 중복을 제거해줌. 

 

    특징

       DB에 데이터 중복이 많고 distinct 사용 안 하면 DB에서 애플리케이션으로 모든 데이터가 나가며 데이터 전송량이           많아짐.

       orders를 DTO로 변환할 때 이미 findAllWithItem에서 item까지 전부 조회하며 캐시에 들어갔으므로 N쿼리 발생 X

 

    2. (V3.1) 페이징 필요할 때 (1:다에서 1을 기준으로 페이징) ( 컬렉션 조회 1+1 )

       1. ToOne(OneToOne, ManyToOne) 관계를 모두 페치조인 한다. ToOne 관계는 row수를 증가시키지 않으므로                   페이징 쿼리에 영향을 주지 않는다.

       2. 컬렉션은 지연 로딩으로 조회한다. (lazy 강제초기화)

       3. 지연 로딩 성능 최적화를 위해 hibernate.default_batch_fetch_size , @BatchSize 를 적용한다.                   

              ※ hibernate.default_batch_fetch_size: 글로벌 설정

              ※ @BatchSize: 개별 최적화 

                              컬렉션은 필드 위에 @BatchSize(size=1000)

                              toOne관계는 엔티티위에 @BatchSize(size=1000)

              ※ 이 옵션을 사용하면 컬렉션이나, 프록시 객체를 한꺼번에 설정한 size 만큼 IN 쿼리로 조회한다.

 

     특징

       DB에 데이터 중복이 없고 DB에서 애플리케이션으로 중복 없는 데이터 전송이 됨.

       = 페치 조인 방식과 비교해서 쿼리 호출 수가 약간 증가하지만, DB 데이터 전송량이 감소한다

DTO로 변환할때 .getItem().getName()을 해주기 때문에 item도 조회됨.

 


2. 엔티티를 DTO로 변환

     1. (V4) toOne관계 먼저 조회하고 orderId로 특정 id에 맞는 orderItems 찾기

 

     특징 

       1(처음 order들 조회하는 쿼리) + N(order 1개마다 items 조회하는 쿼리들) 문제 발생

 

 

     2. (V5) toOne관계 먼저 조회하고 orderId를 string으로 모아 orderItems을 조회하는 in쿼리를 날리기

         -> map<id, list>으로 메모리에 저장해두고 order마다 items자리에 매칭해주기

 

      특징

        1(처음 order들 조회하는 쿼리)  + 1(모든 orderItems 들을 한방에 가져오는 쿼리)  = 쿼리 2번

        원하는 정규화된 데이터만 촥 가져옴

 

 

      3. (V6) 모든걸 조인해서 한방에 가져오기 

      특징

       쿼리는 1번만 날라가지만 flat한 데이터가 생성되므로 다른 DTO로 변경해줘야함.

       모든걸 join해서 DB에 데이터 중복이 되므로 2번보다 느릴 수 도 있다.  (데이터 적으면 이게 더 빠름)

        order를 기준으로 페이징 불가능 (1:다로 데이터가 뻥튀기 되니까)

        (* 결과로 원하는 데이터타입에 @EqualsAndHashCode(of = "orderId") 넣어줘야함. groupingBy는 뭘 기준으로              합칠껀지 명시해줘야함)

 

 

 

 

 

 

fetch join은 한방쿼리 

Batch_fetch도 엔티티마다 size 만큼은 한방으로 쿼리 (to One 관계도 fetch join 안 해주면 batch_fetch 방식으로 가져옴. 하지만 fetch join쓰자~^^!)

페이징이 필요하지 않는 컬렉션 조회에서 데이터가 너무 많다면 중복되는 양도 많고 전송량도 많아짐. => Batch fetch 사용