지연로딩, 즉시로딩

오민석·2021년 7월 28일
0

Eager

JOIN 문으로 관련된 모든 테이블 데이터 가져옴
단, 쿼리 결과 예상을 못함

Lazy`

Proxy

실제 클래스를 상속 받아 만들어진 것으로 사용자 입장에서는 진짜 객체랑 구분하지 않고 사용.
프로시 객체를 호출하면 실제 객체 메소드 호출
단, N+1 성능문제

Order 조회 -> 조회결과 2건
Member, Delivery 테이블 2번씩 조회, 총 5번 조회(최악의 경우)
단, 만약 같은 memer 혹은 delivery 라면 영속성 컨텍스트에서 가져오니까 최악 경우보다는 낫다

    @GetMapping("/api/v2/simple-orders")
    public List<SimpleOrderDto> ordersV2() {
        List<Order> orders = orderRepository.findAllByString(new OrderSearch());
        List<SimpleOrderDto> result = orders.stream()
                .map(o -> new SimpleOrderDto(o)) // SimpleOrderDto::new
                .collect(toList());
        return result;
    }
    
DTO
    @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(); // 이 때 쿼리 나감
            orderDate = order.getOrderDate();
            orderStatus = order.getStatus();
            address = order.getDelivery().getAddress(); // 이 때 쿼리 나감
        }
    }

지연로딩 쿼리 결과 N+1 문제

    select
        order0_.order_id as order_id1_6_,
        order0_.delivery_id as delivery4_6_,
        order0_.member_id as member_i5_6_,
        order0_.order_date as order_da2_6_,
        order0_.status as status3_6_ 
    from
        orders order0_ 
    select
        member0_.member_id as member_i1_4_0_,
        member0_.name as name5_4_0_ 
    from
        member member0_ 
    where
        member0_.member_id=?
    select
        delivery0_.delivery_id as delivery1_2_0_,
        delivery0_.city as city2_2_0_,
        delivery0_.street as street3_2_0_,
    from
        delivery delivery0_ 
    where
        delivery0_.delivery_id=?
    select
        member0_.member_id as member_i1_4_0_,
        member0_.name as name5_4_0_ 
    from
        member member0_ 
    where
        member0_.member_id=?
    select
        delivery0_.delivery_id as delivery1_2_0_,
        delivery0_.city as city2_2_0_,
        delivery0_.street as street3_2_0_,
    from
        delivery delivery0_ 
    where
        delivery0_.delivery_id=?

@BatchSize(size)

lazyLoading 시 연관된 엔티티를 조회할 때 지정된 size 만큼 SQL의 IN절을 사용해서 조회한다

spring.jpa.properties.hibernate.default_batch_fetch_size: 1000
select
        order0_.order_id as order_id1_6_,
        order0_.delivery_id as delivery4_6_,
        order0_.member_id as member_i5_6_,
        order0_.order_date as order_da2_6_,
        order0_.status as status3_6_ 
    from
        orders order0_ 
    inner join
        member member1_ 
            on order0_.member_id=member1_.member_id limit ?
Hibernate: 
    select
        member0_.member_id as member_i1_4_0_,
        member0_.city as city2_4_0_,
        member0_.street as street3_4_0_,
        member0_.zipcode as zipcode4_4_0_,
        member0_.name as name5_4_0_ 
    from
        member member0_ 
    where
        member0_.member_id in (
            ?, ?
        )
Hibernate: 
    select
        delivery0_.delivery_id as delivery1_2_0_,
        delivery0_.city as city2_2_0_,
        delivery0_.street as street3_2_0_,
        delivery0_.zipcode as zipcode4_2_0_,
        delivery0_.status as status5_2_0_ 
    from
        delivery delivery0_ 
    where
        delivery0_.delivery_id in (
            ?, ?
        )

Fetch Join

연관된 테이블을 가져오지만, 프록시 객체가 아닌 실제 객체를 가져온다. 다만, 1:N 관계에서 가져올 떄 다음과 같은 데이터 뻥튀기 문제가 있어서 distinct로 다음과 같이 중복된 entity는 제거한다

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

한계

  • 1:N 경우, 데이터 뻥튀기가 되어서 컬렉션을 페치 조인하면 페이징 API(setFirstResult,
    setMaxResults)를 사용할 수 없다. distinct전에 페이징 쿼리 나간다
  • 일대일, 다대일 같은 단일 값 연관 필드들은 페치 조인해도 페이징 가능
  • db 쿼리에서 limit, offset 쿼리가 없다. 하이버네이트는 경고 로그를 남기고 메모리에서 페이징(매우 위험) out of memory 위험성 -> team => member가 아닌 member => team으로 방향을 바꾼다

Reference
김영한 강사님 실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 강의

0개의 댓글