인프런 실전 스프링부트2 강의 정리[How Hair 플젝]

eunsiver·2023년 4월 3일
0

Spring boot 구현

목록 보기
6/12

전에 이미 다 본 강의인데 플젝을 하면서 다시 보았다.
확실히 플젝을 안해보고 강의만 볼 때와 플젝을 하면서 강의를 볼 때의 와닿는 점은 많이 차이가 나는 것 같다!

그저 아는 것과 해보는 것은 굉장히 다르다.

다시 보며 열심히 정리를 해보자.


DTO와 Entity

실무에서는 엔티티를 위한 API가 다양하게 만들어지는데, 한 엔티티에 각각의 API를 위한 모든 요청 요구사항을 담기는 어렵다.

엔티티가 변경 되면 API 스펙이 변하기 때문에 엔티티로 값을 직접 받지 말고

✅ API 요청 스펙에 맞추어 별로의 DTO를 만들고 이를 파라미터로 받게 하자.

이를 통해
엔티티와 프레젠테이션 계층을 위한 로직을 분리할 수 있다.
엔티티와 API 스펙을 명확하게 분리할 수 있다.
엔티티가 변해도 API 스펙이 변하지 않는다.

중요한 점은 실무에서는 엔티티를 API 스펙에 노출하면 안된다는 것이다.


컬렉션 직접 반환을 별도의 Result 클래스로 변경하자.

현재 플젝에서도 List로 DTO를 넘겨주고 있다.
하지만 컬렉션을 직접 반환하면 향후 API 스펙을 변경하기 어렵기 때문에 별도의 Result 클래스를 생성하여 담아줘야 한다.

이런 식으로 하면 Result 클래스로 컬렉션을 감싸서 향후 필요한 필드를 추가할 수 있다고 한다.

하지만 이번 플젝에서 API 스펙이 변경될 일이 없을 것이라고 생각되어 그대로 나두기로 했다.


지연 로딩과 조회 성능 최적화

@ToOne

이 강의를 다시보는 궁극적인 이유는 플젝에서 조회 성능을 최적화하고 싶기 때문이다. 조회를 하는데 쿼리가 너무 많이 나간다고 느껴졌다. 성능 최적화에 대해 신경을 쓰지 않았다. 차차 변경해보도록 하자.

성능 최적화가 필요한 경우 fetch join을 사용하자.

연관되어 있을때, @ToOne 관계일때, fetch join으로 쿼리를 한 번에 조회할 수 있도록 하자.
프록시가 아닌 진짜 객체 값을 채와서 가져옴

repository의 쿼리를 짜는 것은 queryDsl로 쉽게 할 수 있다.

repository에서 JPA에서 DTO로 바로 조회하는 경우

public List<OrderSimpleQueryDto> findOrderDtos() {
	 return em.createQuery(
	 "select new
jpabook.jpashop.repository.order.simplequery.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();
 }

이렇게 복잡하게 나오는데 QueryDsl을 사용하면 깔끔하게 사용할 수 있다.

다만 repository에서 JPA에서 DTO로 바로 조회하는 이 경우는 리포지토리 재사용성이 떨어지며, API 스펙에 맞춘 코드가 리포지토리에 들어가는 단점이 있다.


컬렉션 조회

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

페치 조인으로 sql이 1번만 실행된다.

JPA의 distinct는 SQL에 distinct를 추가하고, 더해서 같은 엔티티가 조회되면, 애플리케이션에서 중복을 걸러준다. 즈, 페치 조인으로 인해 중복 조회가 되는 것을 막아준다.

하지만 컬렉션 페치 조인을 사용하면 페이징이 불가능하다는 단점이 있다. 하이버네이트는 경고 로그를 남기면서 모든 데이터를 DB에서 읽어오고 메모리에서 페이징을 해버린다.

또한 컬렉션 페치 조인은 1개만 사용할 수 있다. 컬렉션 둘 이상에 페치 조인을 사용하면 데이터가 부정합하게 조합될 수 있다.

컬렉션을 페치 조인할때 문제점 정리

  • 페이징이 불가능
  • 일대다 조인이 발생하므로 데이터가 예측할 수 없이 증가한다.
  • 일대다에서 일(1)을 기준으로 페이징을 하는 것이 목적인데 컬렉션을 페치 조인하면 다(N)을 기준으로 row가 발생한다.
  • 이 경우 하이버네이트는 경고 로그를 남기고 모든 DB 데이터를 읽어서 메모리에서 페이징을 시도하기 때문에 최악의 경우 장애로 이어질 수 있다.

❓ 그렇다면 페이징 + 컬렉션 엔티티를 함께 조회하려면 어떻게 해야할가?

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

컬렉션은 지연 로딩으로 조회한다.

지연 로딩 성능 최적화를 위해

hibernate.default_batch_fetch_size: 글로벌 설정
@BatchSize: 개별 최적화

이 옵션을 사용한다.

이 옵션은 컬렉션이나 프록시 객체를 한꺼번에 설정한 size만큼 IN 쿼리로 조회한다.

🟨 장점

  • 조인보다 DB 데이터 전송량이 최적화된다. 일대다에서 일을 기준으로 조회되기 때문에 전송해야할 중복 데이터가 없다.
  • 페치 조인 방식과 비교해서 쿼리 호출 수가 약간 증가하지만, DB 데이터 전송량이 감소한다.
  • 컬렉션 페치 조인은 페이징이 불가능 하지만 이 방법은 페이징이 가능하다.

즉, ToOne 관계는 페치 조인해도 페이징에 영향을 주지 않는다. 따라서 ToOne 관계는 페치 조인으로 쿼리 수를 줄여 해결하고, 나머지는 hibernate.default_batch_fetch_size로 최적화 하자.

profile
Let's study!

0개의 댓글