[스프링 JPA 활용] WEEK 12

enxnong·2023년 12월 2일
0

김영환님의 강의 실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화 보면서 공부한 내용입니다.

🏊‍♀️ 섹션 4

주문 조회 V3.1: 엔티티를 DTO로 변환 - 페이징과 한계 돌파


📝 특징

  • 컬렉션을 fetch join하면 페이징이 불가능하다
  • 일대다에서 일(1)을 기준으로 페이징을 하는 것이 목적이지만, 데이터는 다(N)을 기준으로 row가 생성된다.
  • 즉, Order(1)을 기준으로 페이징 하고 싶은데, OrderItem(N)을 기준이 되는 것이다.

💡 해결법

  • ToOne관계는 페치 조인한다
  • 컬렉션은 지연로딩으로 조회한다
  • 지연 로딩 성능 최적화를 위해 hibernate.default_batch_fetch_size, @BatchSize를 적용한다
    → hibernate.default_batch_fetch_size : 글로벌 설정 (적어온 갯수만큼 미리 땡겨옴)
    → @BatchSize : 개별 최적화
    → 이 옵션을 사용하면 컬렉션이나 프록시 객체를 한꺼번에 설정한 size만큼 IN 쿼리로 조회한다
/* 방법 1 */
// application.yml
default_batch_fetch_size: 100 # IN 조건 추가해서 한 번에 100개 조회가능
// 사이즈 중요!
// 사이즈는 100 ~ 1000 사이를 선택하는 것을 권장

/* 방법 2 */
// Order.java 
@BatchSize(size=1000) // 컬렉션인 경우
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();

// Item.java
@BatchSize(size = 1000) // 컬렉션이 아닌 경우
@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class OrderItem {

✅ 쿼리 호출 수가 1+N에서 1+1으로 최적화 된다
✅ 조인보다 DB 데이터 전송량이 최적화 된다
✅ 페이징 처리가 가능하다

주문 조회 V4: JPA에서 DTO 직접 조회


📝 특징

  • 페이지 가능
  • 쿼리 : 루트 1번 + 컬렉션 N번 실행
  • ToOne(N:1, 1:1) 관계들을 먼저 조회하고 ToMany(1:N) 관계는 각각 별도로 처리
    ✅ ToOne 관계는 조인해도 데이터 row 수가 늘어나지 않지만, ToMany 관계는 조인하면 증가한다
  • ToOne 관계는 조인으로 최적화하기 쉬우므로 한 번에 조회하고 ToMany 관계는 별도의 메서드로 조회한다
    private List<OrderItemQueryDto> findOrderItems(Long orderId) {
        return em.createQuery(
                "select new jpabook.jpashop.repository.order.query.OrderItemQueryDto(oi.order.id, i.name, oi.orderPrice, oi.count)" +
                        " from OrderItem oi" +
                        " join oi.item i" +
                        " where oi.order.id = :orderId", // OrderItem에서 item은 "ToOne" 관계이므로 join 으로 처리함OrderItemQueryDto.class)
                .setParameter("orderId", orderId)
                .getResultList();
    }

📝 문제점

  • 1 + N

주문 조회 V5: JPA에서 DTO 직접 조회 - 컬렉션 조회 최적화

📝 특징

  • 쿼리 : 루트 1번 + 컬렉션 1번 실행
  • ToOne(N:1, 1:1) 관계들을 먼저 조회하고 식별자 orderId로 ToMany관계인 OrderItem을 한꺼번에 조회
  • Map을 사용해서 매칭 성능 향상
    public List<OrderQueryDto> findAllByDto_optimization() {
        List<OrderQueryDto> result = findOrders();

        List<Long> orderIds = toOrderIds(result);

        Map<Long, List<OrderItemQueryDto>> orderItemMap = findOrderItemMap(orderIds);

        result.forEach(o -> o.setOrderItems(orderItemMap.get(o.getOrderId())));

        return result;
    }

주문 조회 V6: JPA에서 DTO로 직접 조회, 플랫 데이터 최적화

📝 특징

  • 쿼리 : 1번

📝 단점

  • 쿼리는 한 번이지만 조인으로 인해 DB에서 애플리케이션에 전달하는 데이터에 중복 데이터가 추가되므로 상황에 따라 더 느릴 수 있다
  • 애플리케이션에서 추가 작업이 크다
  • 페이징 불가능
    public List<OrderFlatDto> findAllByDto_flat() {
        return em.createQuery(
                "select new "
                + "jpabook.jpashop.repository.order.query.OrderFlatDto(o.id, m.name, o.orderDate, o.status, d.address, i.name, oi.orderPrice, oi.count)"
                + " from Order o"
                + " join o.member m"
                + " join o.delivery d"
                + " join o.orderItems oi"
                + " join oi.item i", OrderFlatDto.class)
                .getResultList();
    }

API 개발 고급 정리

📝 권장순서

  • 엔티티 조회 방식으로 우선 접근
    ✅ 페치 조인으로 쿼리 수를 최적화
    ✅ 컬렉션 최적화
    → 페이징 필요 : hibernate.default_batch_fetch_size, @BatchSize 로 최적화
    → 페이징 필요X : 페치 조인 사용
    ✅ 엔티티 조회 방식으로 해결이 안되면 DTO 조회 방식 사용
    ✅ DTO 조회 방식으로 해결이 안되면 NativeSQL 또는 스프링 JdbcTemplate 사용

💡
엔티티 조회 방식은 JPA가 많은 부분을 최적화 해주기 때문에 단순한 코드를 유지하면서 성능을 최적화할 수 있다
반면에 DTO 조회 방식은 SQL을 직접 다루는 것과 유사하기 때문에 둘 사이에 줄타기를 해야한다

🏊‍♀️ 섹션 5

OSIV와 성능 최적화

📝 OSIV

  • Open Session In View : 하이버네이트
  • Open EntityManager In View : JPA
    (관례상 OSIV라 한다)

📝 OSIV ON

  • spring.jpa.open-in-view : true 기본값
  • 트랜잭션 시작처럼 최초 데이터베이스 커넥션 시작 시점부터 API 응답이 끝날 때 까지 영속성 컨텍스트와 데이터베이스 커넥션을 유지한다

✅ OSIV ON의 장점

  • 지연 로딩이 가능하다
    → 지연 로딩은 영속성 컨텍스트가 살아있어야 가능하고, 영속성 컨텍스트는 기본적으로 데이터베이스 커넥션을 유지한다.
  • 유지보수가 용이하다

✅ OSIV ON의 단점

  • 실시간 트랙픽이 중요한 애플리케이션에서 커넥션이 모자랄 수 있다
    → 향후 장애로 이어짐
    → ADMIN처럼 커넥션을 많이 사용하지 않는 곳에서 OSIV ON을 하는 것이 좋다
    EX) 컨트롤러에서 외부 API를 호출하면 외부 API 대기 시간 만큼 커넥션 리소스를 반환하지 못하고 유지해야 한다

📝 OSIV OFF

  • spring.jpa.open-in-view : false
  • 트랜잭션을 종료할 때 영속성 컨텍스트를 닫고, 데이터베이스 커넥션도 반환

✅ OSIV OFF의 장점

  • 커넥션 리소스를 낭비하지 않는다
    → 고객 서비스의 실시간 API는 OSIV OFF를 하는 것이 좋다

✅ OSIV OFF의 단점

  • 모든 지연로딩을 트랜잭션 안에서 처리해야 한다.
    → view template에서 지연로딩이 동작하지 않으므로 트랜잭션이 끝나기 전에 지연 로딩을 강제로 호출해야 한다
profile
높은 곳을 향해서

0개의 댓글