인프런 커뮤니티 질문&답변

닉네임님의 프로필 이미지
닉네임

작성한 질문수

실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화

간단한 주문 조회 V3: 엔티티를 DTO로 변환 - 페치 조인 최적화

페치조인 쿼리시 쿼리가 두번 나갑니다.

해결된 질문

작성

·

707

0

강의에서 보면 쿼리가 한번 나가는데 저는 두번이 나갑니다 ㅠㅠ

 

http://localhost:8080/api/v3/simple-orders 호출

2022-06-26 01:02:45.805 DEBUG 5062 --- [nio-8080-exec-6] org.hibernate.SQL                        : 

    select

        order0_.order_id as order_id1_6_0_,

        member1_.member_id as member_i1_4_1_,

        delivery2_.delivery_id as delivery1_2_2_,

        order0_.delivery_id as delivery4_6_0_,

        order0_.member_id as member_i5_6_0_,

        order0_.order_date as order_da2_6_0_,

        order0_.status as status3_6_0_,

        member1_.city as city2_4_1_,

        member1_.street as street3_4_1_,

        member1_.zipcode as zipcode4_4_1_,

        member1_.name as name5_4_1_,

        delivery2_.city as city2_2_2_,

        delivery2_.street as street3_2_2_,

        delivery2_.zipcode as zipcode4_2_2_,

        delivery2_.status as status5_2_2_ 

    from

        orders order0_ 

    inner join

        member member1_ 

            on order0_.member_id=member1_.member_id 

    inner join

        delivery delivery2_ 

            on order0_.delivery_id=delivery2_.delivery_id

Hibernate: 

    select

        order0_.order_id as order_id1_6_0_,

        member1_.member_id as member_i1_4_1_,

        delivery2_.delivery_id as delivery1_2_2_,

        order0_.delivery_id as delivery4_6_0_,

        order0_.member_id as member_i5_6_0_,

        order0_.order_date as order_da2_6_0_,

        order0_.status as status3_6_0_,

        member1_.city as city2_4_1_,

        member1_.street as street3_4_1_,

        member1_.zipcode as zipcode4_4_1_,

        member1_.name as name5_4_1_,

        delivery2_.city as city2_2_2_,

        delivery2_.street as street3_2_2_,

        delivery2_.zipcode as zipcode4_2_2_,

        delivery2_.status as status5_2_2_ 

    from

        orders order0_ 

    inner join

        member member1_ 

            on order0_.member_id=member1_.member_id 

    inner join

        delivery delivery2_ 

            on order0_.delivery_id=delivery2_.delivery_id

 

전체 코드

Order 엔티티

package jpabook.jpashop.domain.entity;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "orders")
@Getter @Setter
public class Order {

@Id @GeneratedValue
@Column(name = "order_id")
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;

@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();

@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "delivery_id")
private Delivery delivery;

private final LocalDateTime orderDate = LocalDateTime.now();

@Enumerated(EnumType.STRING)
private OrderStatus status;

//==연관관계 메서드==//
public void setMember(Member member) {
this.member = member;
member.getOrders().add(this);
}

public void addOrderItem(OrderItem orderItem) {
orderItems.add(orderItem);
orderItem.setOrder(this);
}

public void setDelivery(Delivery delivery) {
this.delivery = delivery;
delivery.setOrder(this);
}

//==생성 메서드==//
public static Order createOrder(Member member, Delivery delivery, OrderItem... orderItems) {
Order order = new Order();
order.setMember(member);
order.setDelivery(delivery);

for (OrderItem orderItem : orderItems) {
order.addOrderItem(orderItem);
}

order.setStatus(OrderStatus.ORDER);
return order;
}

//==비즈니스 로직==/
/**
* 주문 취소
*/
public void cancel() {
if (delivery.getStatus() == DeliveryStatus.COMP) {
throw new IllegalStateException("이미 배송완료된 상품은 취소가 불가능합니다.");
}

this.setStatus(OrderStatus.CANCEL);
for (OrderItem orderItem : orderItems) {
orderItem.cancel();
}
}

//==조회 로직==//
/**
* 전체 주문 가격 조회
*/
public int getTotalPrice() {
return orderItems.stream().mapToInt(OrderItem::getTotalPrice).sum();
}
}

controller

package jpabook.jpashop.api;

import jpabook.jpashop.domain.entity.Address;
import jpabook.jpashop.domain.entity.Order;
import jpabook.jpashop.domain.entity.OrderStatus;
import jpabook.jpashop.domain.repository.OrderRepository;
import jpabook.jpashop.domain.repository.OrderSearch;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

/**
* xToOne(ManyToOne, OneToOne)
* Order
* Order -> Member
* Order -> Delivery
*/
@Slf4j
@RestController
@RequiredArgsConstructor
public class OrderSimpleApiController {

private final OrderRepository orderRepository;

@GetMapping("/api/v1/simple-orders")
public List<Order> ordersV1() {

List<Order> all = orderRepository.findAllByString(new OrderSearch());
all.stream().forEach(o -> {
o.getMember().getName(); // Lazy 강제 초기화
o.getDelivery().getAddress(); // Lazy 강제 초기화
});
return all;
}

@GetMapping("/api/v2/simple-orders")
public List<SimpleOrderDto> ordersV2() {
List<Order> orders = orderRepository.findAllByString(new OrderSearch());

return orders.stream()
.map(SimpleOrderDto::new)
.collect(Collectors.toList());
}

@GetMapping("/api/v3/simple-orders")
public List<SimpleOrderDto> ordersV3() {
List<Order> orders = orderRepository.findAllWithMemberDelivery(new OrderSearch());

return orders.stream()
.map(SimpleOrderDto::new)
.collect(Collectors.toList());
}

@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();
this.address = order.getDelivery().getAddress();
}
}
}

repository

package jpabook.jpashop.domain.repository;

import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.List;
import jpabook.jpashop.domain.entity.Order;

@Repository
public class OrderRepository {

private final EntityManager em;

public OrderRepository(EntityManager em) {
this.em = em;
}

public void save(Order order) {
em.persist(order);
}

public Order findOne(Long id) {
return em.find(Order.class, id);
}

public List<Order> findAllByString(OrderSearch orderSearch) {

String jpql = "select o from Order o join o.member m";
boolean isFirstCondition = true;

//주문 상태 검색
if (orderSearch.getOrderStatus() != null) {
if (isFirstCondition) {
jpql += " where";
isFirstCondition = false;
} else {
jpql += " and";
}
jpql += " o.status = :status";
}

//회원 이름 검색
if (StringUtils.hasText(orderSearch.getMemberName())) {
if (isFirstCondition) {
jpql += " where";
isFirstCondition = false;
} else {
jpql += " and";
}
jpql += " m.name like :name";
}

TypedQuery<Order> query = em.createQuery(jpql, Order.class)
.setMaxResults(1000);

if (orderSearch.getOrderStatus() != null) {
query = query.setParameter("status", orderSearch.getOrderStatus());
}
if (StringUtils.hasText(orderSearch.getMemberName())) {
query = query.setParameter("name", orderSearch.getMemberName());
}

return query.getResultList();
}

/**
* JPA Criteria
*/
public List<Order> findAllByCriteria(OrderSearch orderSearch) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Order> cq = cb.createQuery(Order.class);
Root<Order> o = cq.from(Order.class);
Join<Object, Object> m = o.join("member", JoinType.INNER);

List<Predicate> criteria = new ArrayList<>();

//주문 상태 검색
if (orderSearch.getOrderStatus() != null) {
Predicate status = cb.equal(o.get("status"), orderSearch.getOrderStatus());
criteria.add(status);
}
//회원 이름 검색
if (StringUtils.hasText(orderSearch.getMemberName())) {
Predicate name =
cb.like(m.<String>get("name"), orderSearch.getMemberName());
criteria.add(name);
}

cq.where(cb.and(criteria.toArray(new Predicate[criteria.size()])));
TypedQuery<Order> query = em.createQuery(cq).setMaxResults(1000);
return query.getResultList();
}

public List<Order> findAllWithMemberDelivery(OrderSearch orderSearch) {
return em.createQuery("select o from Order o join fetch o.member m join fetch o.delivery d", Order.class)
.getResultList();
}
}

 

답변 1

1

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. 스프링 최고가 될꺼야님

실제 한번 실행되는 것이 맞습니다.

하나를 로거를 통해서 로그가 출력되고 있고, 나머지 하나는 System.out을 통해서 로그가 출력되고 있습니다.

application.yml에서 

show_sql 부분을 false로 처리해주세요. 그러면 System.out을 통해서 출력되는 로그가 제거됩니다.

감사합니다.

닉네임님의 프로필 이미지
닉네임

작성한 질문수

질문하기