-
SQL Query 최적화 (Spring JPA, Go gorm)스터디 2024. 3. 15. 23:34
일반적으로 서버와 DB에서 Query를 만들때
성능적으로 가장 신경써야할 부분은 조회이다
왜 ?
가장 많이 사용될 뿐더러, 일반적으로 가장 많은 데이터들을 scan하고 fetch하기 때문이다
그렇다면 ?
Query 성능 최적화를 하기 위해서는 select 쿼리를 신경쓰는것이 중요하다.
특히 OneToMany, ManyToMany, ManyToOne 과 같은 연관관계가 존재하는 Table일 경우에 최적화의 중요성은 강조된다.
Spring
Spring JPA에서는 '지연로딩'과 '즉시로딩' 이라는 방법이 존재한다.
@Data 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(); orderItems = order.getOrderItems().stream() .map(orderItem -> new OrderItemDto(orderItem)) .collect(toList()); } }
현재 OrderDto는 OrderItemsDto와 일대다 매핑이 되어있는 상태이다.
즉시로딩이란 ?
OrderDto을 조회하면서 연관된 OrderITemDto 모두 조회한다
이러한 방법은 편하긴 하지만 만약 Order Entity가 OrderItem이 필요하지 않을때도 조회를 진행한다. 만약 연관된 리스트의 값이 엄청 많으면 ? 그만큼 필요없는 query가 발생하고 이는 성능저하로 이어질 수 있다.
이러한 이유로 Spring JPA에서는 지연로딩을 많이 사용한다.
지연로딩이란 ?
연관된 List들을 가져올때 Proxy 형태로 가져온다. 따라서 OrderDto에서는 처음에 가져올때는 ORderItemDTO가 값이 없고 Proxy형태로 존재한다. 만약 OrderItemDto를 조회하려고 할때 그 시점에서 List Item에 대한 Fetch가 발생한다.
방법 1
@GetMapping("/api/v2/orders") public List<OrderDto> ordersV2() { List<Order> orders = orderRepository.findAll(); List<OrderDto> result = orders.stream() .map(o -> new OrderDto(o)) .collect(toList()); return result; } public List<Order> findAll() { return em.createQuery("select o from Order o", Order.class) .getResultList(); }
일단 전부다 가져온 후에 OrderItem을 순회하며 Lazy 강제초기화 시켜주는 방식이다.
OrderItem의 수만큼 쿼리가 수행되기 때문에 비효율적이라고 볼 수 있다 .
방법 2
@GetMapping("/api/v3/orders") public List<OrderDto> ordersV3() { log.info("V3 orders"); StopWatch stopWatch = new StopWatch(); stopWatch.start(); List<Order> orders = orderRepository.findAllWithItem(); List<OrderDto> result = orders.stream() .map(o -> new OrderDto(o)) .collect(toList()); stopWatch.stop(); log.info(stopWatch.prettyPrint()); return result; } 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(); }
FetchJoin을 사용하는 방법이다
단 한번의 쿼리로 모든 데이터를 조회가 가능하다.
JPA가 신기한게 fetch로 모든데이터를 가져온 후에 Order와 OrderITem들을 자동적으로 매핑시켜준다.
하지만 OneToMany의 관계를 FetchJoin하는 경우에 페이징이 불가능하다.
'스터디' 카테고리의 다른 글
Spring Transactional 뜯어보기 (0) 2024.05.15 Spring Tomcat 분석 (0) 2024.05.01 Spring JPA (0) 2024.01.24 Spring - Annotation 정리 (2) 2023.12.01 Spring - 기본 개념 (0) 2023.12.01