[내일배움캠프 사전캠프] 2일 차 TIL 상품-주문 미니프로젝트

2026. 2. 5. 18:05·내배캠

상품-주문 미니프로젝트 2일 차

스프링 입문주차 강의를 수강을 진행하고, 프로젝트를 더 진행했다.

Swagger vs Postman

1일 차 때 swagger에 대해서 배웠는데,
Postman도 사용해 봤다.

GET 요청을 통해 DB에 저장된 데이터를 확인

먼저 swagger는 gradle로 관리할 수 있는 라이브러리였다.
그리고 swagger에서 제공하는 html로 직접 테스트했다.

하지만 postman은 앱을 다운받아서 실행하여 테스트할 수 있다.

둘 다 현재 실행되고 있는 로컬 환경의 백엔드서버 url로 접근해서 요청을 보내는 방식이지만
라이브러리냐 소프트웨어냐의 차이이다.

 

둘 중 뭐가 더 좋은지는 좀 더 사용해 봐야 알 것 같다.

스프링 vs 스프링부트

Apache에서 제공하는
Apache Server = 웹서버, 정적 파일 서빙
Tomcat = 웹 애플리케이션 서버(WAS), 동적 처리 담당
Apache Tomcat = 웹서버 + WAS, 정적 + 동적처리

Apache Tomcat을 직접 다운로드해서 프로젝트에 삽입해줘야 했던 스프링과 달리
스프링부트는 Apache Tomcat을 내장하고 있다.

Gradle 빌드

intellJ의 gradle 탭에서 빌드를 더블클릭해 현재 작성된 코드를 기준으로 빌드를 진행할 수 있다.
이를 통해 어노테이션의 자동 코드 생성 기능이
실제로 어떻게 적용되었는지 눈으로 코드를 확인해 볼 수 있다.

 

상품-주문 미니프로젝트 내용

RESTFul은 컨트롤러에서
url 은 자원(Resource)
HTTP 메서드는 행위를 의미해서
엔드포인트 경로에 행위를 적으면 안 된다고 한다.

GET /products/list/ -> x
GET /products/{id} -> o
GET /products -> o

목록 조회 시 N+1 조회 문제

주문 목록을 조회할 때 

주문 엔티티를 먼저 불러오고
엔티티를 응답 dto로 변환하는 과정에서
연관 관계에 있는 상품 객체에 접근하게 되는데 이 과정에서 N+1 조회 문제가 발생한다.

 

문제 코드 예시)

public List<OrderResponse> findAll(){
        List<Order> orders = orderRepository.findAll(); // 조회 1번
        List<OrderResponse> orderResponseList = new ArrayList<>();
        for (Order order : orders) {
            OrderResponse orderResponse = new OrderResponse(
                    order.getId(),
                    order.getProduct().getName(), // 조회 n번
                    order.getProduct().getPrice()
            );
            orderResponseList.add(orderResponse);
        }
        return orderResponseList;
    }


Order 조회 1번 + Product 조회 N번 = N+1번 조회하게 된다.

해결방법은 쿼리문을 한 번만 사용하는 것이다.
그래서 N+1을 1번으로 줄일 수 있다.


그 방법으로는 처음부터 연관 관계 매핑된 것들까지 한 번에 쿼리로 가져오는 join fetch 가 있다.

// 리포지토리
public interface OrderRepository extends JpaRepository<Order,Long> {

    @Query("""
        select o
        from Order o
        join fetch o.product
    """)
    List<Order> findAllWithProduct();
}

JPQL( Java Persistence Query Language )를 통해서
직접 쿼리문을 작성한다. 이때 join fetch문을 통해 연관 매핑된 클래스 정보까지 가져올 수 있다.

// 서비스 코드
public List<OrderResponse> findAll(){
        List<Order> orders = orderRepository.findAllWithProduct(); // 1번
        List<OrderResponse> orderResponses = new ArrayList<>();
        for (Order order : orders) {
            OrderResponse orderResponse = new OrderResponse(
                    order.getId(),
                    order.getProduct().getName(), // 위에서 이미 가져왔기때문에 쿼리문 실행 x
                    order.getProduct().getPrice()
            );
            orderResponses.add(orderResponse);
        }
        return orderResponses;
    }


주문 목록을 조회할 때 나오는 로그)

Hibernate: 
    select
        o1_0.id,
        o1_0.product_id,
        p1_0.id,
        p1_0.name,
        p1_0.price 
    from
        orders o1_0 
    join
        product p1_0 
            on p1_0.id=o1_0.product_id


반면에 N+1의 경우 나오는 로그)

Hibernate: 
    select
        o1_0.id,
        o1_0.product_id 
    from
        orders o1_0
Hibernate: 
    select
        p1_0.id,
        p1_0.name,
        p1_0.price 
    from
        product p1_0 
    where
        p1_0.id=?
Hibernate: 
    select
        p1_0.id,
        p1_0.name,
        p1_0.price 
    from
        product p1_0 
    where
        p1_0.id=?

order 목록을 먼저 조회하고 ( 1번 조회 )
order에 product 필드를 위해 2번 더 조회한 모습이다. ( n번조회, 이 경우엔 2번)

DB에는 ORDER 테이블에 2개의 행이 있는 상태였다.


두 번째 방법은 엔티티에 접근하지 않고 바로 필요한 컬럼에 접근하는 DTO Projection 방식이 있다.

쿼리 조회결과를 바로 dto로 가져올 수 있다.

 

예시) OrderDTO 직접 조회

// 리포지토리 예시
@Query("""
    select new boot.hyun.shop.dto.OrderResponse(
        o.id,
        p.name,
        p.price
    )
    from Order o
    join o.product p
""")
List<OrderResponse> findAllOrderDtos();


DB -> DTO의 순서로 바로 진행하는 Dto Projection에 비해

join fetch는 DB -> 엔티티 -> DTO의 과정으로 엔티티를 한 번 거친다.

 

상품 재고 차감 구현

상품을 주문했을때 주문한 상품의 수량만큼 상품의 재고가 줄어들어야 한다.

// Product 엔티티 클래스
// 재고 차감 메서드
public void decreaseStock(Integer count){
    if(this.stock < count){
        throw new RuntimeException("재고가 부족합니다.");
    }
    this.stock -= count;
}

먼저 Product 엔티티에서 재고 차감 메서드를 추가해줬다.

 

그리고 주문 서비스 레이어에서 주문 생성시 재고 차감 메서드를 추가해
원자성을 고려해봤다.

// Order 서비스레이어
// 주문 생성 메서드
public Long save(OrderRequest orderRequest) {
    Product product = productRepository.findByNameAndPrice(
                orderRequest.getProductName(),
                orderRequest.getProductPrice()
            ).orElseThrow(()->new RuntimeException("[저장시 에러]-해당 상품 없음"));
    Order order = new Order(product);
    orderRepository.save(order);
    // 이 자리에 재고 차감 메서드 실행
    product.decreaseStock(orderRequest.getProductStock());
    productRepository.save(product);
    return order.getId();
}


재고가 0일때 상품 1개를 추가 주문한 경우

위와같이 에러가 난다.

진행한 프로젝트 코드 - Github url) https://github.com/RunSBS/sparta-mini1

'내배캠' 카테고리의 다른 글

[내일배움캠프 본캠프] 3일 차 TIL 영속성 컨텍스트  (0) 2026.02.12
[내일배움캠프 본캠프] 2일 차 TIL - SQL, JdbcTemplate, IoC, DI, Bean  (0) 2026.02.11
[내일배움캠프 본캠프] 1일 차 TIL 상품-주문 미니프로젝트 피드백  (0) 2026.02.10
[내일배움캠프 사전캠프] 3일 차 TIL Jackson  (0) 2026.02.06
[내일배움캠프 사전캠프] 1일 차 TIL 상품-주문 미니프로젝트  (0) 2026.02.04
'내배캠' 카테고리의 다른 글
  • [내일배움캠프 본캠프] 2일 차 TIL - SQL, JdbcTemplate, IoC, DI, Bean
  • [내일배움캠프 본캠프] 1일 차 TIL 상품-주문 미니프로젝트 피드백
  • [내일배움캠프 사전캠프] 3일 차 TIL Jackson
  • [내일배움캠프 사전캠프] 1일 차 TIL 상품-주문 미니프로젝트
MvA
MvA
백엔드 개발자 김재현입니다. 주로 공부하면서 느낀점을 기록합니다.
  • MvA
    Man vs Ai
    MvA
  • 전체
    오늘
    어제
    • 분류 전체보기 (94)
      • Java (6)
      • Python (8)
        • 딥러닝 (1)
        • 머신러닝 (7)
      • JavaScript (2)
      • 내배캠 (60)
      • 개인 프로젝트 (11)
      • 책 후기 (5)
      • 기타 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Riot API
    머신러닝
    딥러닝
    내일배움캠프
    TiL
    아키텍처
    배포
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
MvA
[내일배움캠프 사전캠프] 2일 차 TIL 상품-주문 미니프로젝트
상단으로

티스토리툴바