Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

컬렉션 조회 최적화 본문

인프런/[인프런] Springboot와 JPA활용 2

컬렉션 조회 최적화

Seung__ 2021. 8. 24. 00:42

컬렉션인 일대다 관계를 조회하고, 최적화하자.

 

일대다 조회는 DB입장에서 data가 뻥튀기 된다.

 

예를들어 하나의 주문(orderA)에 item( #1, #2, #3)이 3개라면 

data는 1개가 아니라 3개가 된다.

orderA - #1, orderA - #2, orderA-#3  

 

1. 엔티티 직접 노출 : V1


  • get을 통해 member, orderItem, Delivery 프록시를 강제로 초기화 시킨다.
@RestController
@RequiredArgsConstructor
public class OrderApiController {
    
    private final OrderRepository orderRepository;
    
    @GetMapping("/api/v1/orders")
    public List<Order> ordersV1(){
        List<Order> all = orderRepository.findAllByString(new OrderSearch());

        for (Order order : all) {
            order.getMember().getName();
            order.getDelivery().getAddress();
            List<OrderItem> orderItems = order.getOrderItems();
            orderItems.stream().forEach(o -> o.getItem().getName());
        }

        return all;
    }
}
  • 반환된 결과이다.
[
    {
        "id": 4,
        "member": {
            "id": 1,
            "name": "userA",
            "address": {
                "city": "서울",
                "street": "송파",
                "zipcode": "232"
            }
        },
        "orderItems": [
            {
                "id": 6,
                "item": {
                    "id": 2,
                    "name": "JPA1 BOOK",
                    "price": 10000,
                    "stockQuantity": 99,
                    "categories": null,
                    "author": null,
                    "isbn": null
                },
                "orderPrice": 10000,
                "count": 1,
                "totalPrice": 10000
            },
            {
                "id": 7,
                "item": {
                    "id": 3,
                    "name": "JPA2 BOOK",
                    "price": 20000,
                    "stockQuantity": 98,
                    "categories": null,
                    "author": null,
                    "isbn": null
                },
                "orderPrice": 20000,
                "count": 2,
                "totalPrice": 40000
            }
        ],
        "orderDate": "2021-08-23T23:33:07.505188",
        "status": "ORDER",
        "totalPrice": 50000
    },
    {
        "id": 11,
        "member": {
            "id": 8,
            "name": "userB",
            "address": {
                "city": "부산",
                "street": "강남남",
                "zipcode": "222"
            }
        },
        "orderItems": [
            {
                "id": 13,
                "item": {
                    "id": 9,
                    "name": "spring1 BOOK",
                    "price": 20000,
                    "stockQuantity": 197,
                    "categories": null,
                    "author": null,
                    "isbn": null
                },
                "orderPrice": 10000,
                "count": 3,
                "totalPrice": 30000
            },
            {
                "id": 14,
                "item": {
                    "id": 10,
                    "name": "spring2 BOOK",
                    "price": 40000,
                    "stockQuantity": 296,
                    "categories": null,
                    "author": null,
                    "isbn": null
                },
                "orderPrice": 20000,
                "count": 4,
                "totalPrice": 80000
            }
        ],
        "orderDate": "2021-08-23T23:33:07.589062",
        "status": "ORDER",
        "totalPrice": 110000
    }
]
  • 하지만 엔티티를 직접 노출하는건 좋지 않아..

 

2. V2 : DTO로 변환


  • DTO를 반환할 때 안에 엔티티가 있으면 안됨
  • 어쨌든 DTO안에 엔티티가 있으면 외부로 엔티티가 노출되는 것과 같음!
  @Getter
    static class OrderDto{

        private Long orderId;
        private String name;
        private LocalDateTime orderDate;
        private OrderStatus orderStatus;
        private Address address;
        private List<OrderItem> orderItems;

        public OrderDto(Order order) {
            orderId = order.getId();
            name = order.getMember().getName();
            orderDate = order.getOrderDate();
            orderStatus = order.getStatus();
            address = order.getDelivery().getAddress();
            order.getOrderItems().stream().forEach(o -> o.getItem().getName());//강제 초기화.
            orderItems = order.getOrderItems();
        }

    }
  • OrderItem 엔티티가  DTO로 감싸여서 외부로 노출됨..ㅜ
[
    {
        "orderId": 4,
        "name": "userA",
        "orderDate": "2021-08-23T23:52:55.458853",
        "orderStatus": "ORDER",
        "address": {
            "city": "서울",
            "street": "송파",
            "zipcode": "232"
        },
        "orderItems": [
            {
                "id": 6,
                "item": {
                    "id": 2,
                    "name": "JPA1 BOOK",
                    "price": 10000,
                    "stockQuantity": 99,
                    "categories": null,
                    "author": null,
                    "isbn": null
                },
                "orderPrice": 10000,
                "count": 1,
                "totalPrice": 10000
            },
            {
                "id": 7,
                "item": {
                    "id": 3,
                    "name": "JPA2 BOOK",
                    "price": 20000,
                    "stockQuantity": 98,
                    "categories": null,
                    "author": null,
                    "isbn": null
                },
                "orderPrice": 20000,
                "count": 2,
                "totalPrice": 40000
            }
        ]
    },
    {
        "orderId": 11,
        "name": "userB",
        "orderDate": "2021-08-23T23:52:55.506581",
        "orderStatus": "ORDER",
        "address": {
            "city": "부산",
            "street": "강남남",
            "zipcode": "222"
        },
        "orderItems": [
            {
                "id": 13,
                "item": {
                    "id": 9,
                    "name": "spring1 BOOK",
                    "price": 20000,
                    "stockQuantity": 197,
                    "categories": null,
                    "author": null,
                    "isbn": null
                },
                "orderPrice": 10000,
                "count": 3,
                "totalPrice": 30000
            },
            {
                "id": 14,
                "item": {
                    "id": 10,
                    "name": "spring2 BOOK",
                    "price": 40000,
                    "stockQuantity": 296,
                    "categories": null,
                    "author": null,
                    "isbn": null
                },
                "orderPrice": 20000,
                "count": 4,
                "totalPrice": 80000
            }
        ]
    }
]
  • 따라서 엔티티에 대한 의존을 완전히 끊어야함.
  • 이 사례의 경우 orderItem을 변경하면 결국 DTO가 변경되고 결국 API Spec이 변경된다 ㅜ
  • 완전히 DTO로 바꿔야함.
  • OrderItem도 DTO로 바꾸자!!!
    @GetMapping("/api/v2/orders")
    public List<OrderDto> ordersV2(){
        List<Order> orders = orderRepository.findAllByString(new OrderSearch());
        List<OrderDto> collect = orders.stream().map(o -> new OrderDto(o)).collect(Collectors.toList());
        return collect;
    }

    @Getter
    static class OrderDto{

        private Long orderId;
        private String name;
        private LocalDateTime orderDate;
        private OrderStatus orderStatus;
        private Address address;
        private List<OrderItemDto> orderItems;

        public OrderDto(Order order) {
            orderId = order.getId();
            name = order.getMember().getName();
            orderDate = order.getOrderDate();
            orderStatus = order.getStatus();
            address = order.getDelivery().getAddress();
            order.getOrderItems().stream().forEach(o -> o.getItem().getName());//강제 초기화.
            orderItems = order.getOrderItems().stream().map(orderItem -> new OrderItemDto(orderItem)).collect(Collectors.toList());
        }

    }

    @Getter
    static class OrderItemDto{

        private String itemName;
        private int orderPrice;
        private int count;


        public OrderItemDto(OrderItem orderItem) {
            itemName = orderItem.getItem().getName();
            orderPrice = orderItem.getOrderPrice();
            count = orderItem.getCount();
        }
    }
  • ㅋㅋㅋ내가 원하는 정보만 출력되었다.
[
    {
        "orderId": 4,
        "name": "userA",
        "orderDate": "2021-08-24T00:01:53.81268",
        "orderStatus": "ORDER",
        "address": {
            "city": "서울",
            "street": "송파",
            "zipcode": "232"
        },
        "orderItems": [
            {
                "itemName": "JPA1 BOOK",
                "orderPrice": 10000,
                "count": 1
            },
            {
                "itemName": "JPA2 BOOK",
                "orderPrice": 20000,
                "count": 2
            }
        ]
    },
    {
        "orderId": 11,
        "name": "userB",
        "orderDate": "2021-08-24T00:01:53.868504",
        "orderStatus": "ORDER",
        "address": {
            "city": "부산",
            "street": "강남남",
            "zipcode": "222"
        },
        "orderItems": [
            {
                "itemName": "spring1 BOOK",
                "orderPrice": 10000,
                "count": 3
            },
            {
                "itemName": "spring2 BOOK",
                "orderPrice": 20000,
                "count": 4
            }
        ]
    }
]
  • 근데 참고로 이렇게 루트에 json배열로 하지 말고 data로 한번 감싸서 나가야 다른 데이터 추가가 가능.
   @Data
    @AllArgsConstructor
    static class Result<T>{
        private T data;
    }
    @GetMapping("/api/v2/orders")
    public Result ordersV2(){
        List<Order> orders = orderRepository.findAllByString(new OrderSearch());
        List<OrderDto> collect = orders.stream().map(o -> new OrderDto(o)).collect(Collectors.toList());
        return new Result(collect);
  • 요렇게 data안에 결과를 넣어줘야 json구성이 다른 다른 데이터 추가가 가능함!

  • 와 근데 쿼리가 너무 많이나가......order는 2개인데 쿼리가 11개나 나가.. 끔찍..
  • 최적화 절실함..

 

3. GitHub : 210823 collection조회 최적화


 

GitHub - bsh6463/SpringBootJPA1

Contribute to bsh6463/SpringBootJPA1 development by creating an account on GitHub.

github.com

 

Comments