상품-주문 미니프로젝트 2일 차
스프링 입문주차 강의를 수강을 진행하고, 프로젝트를 더 진행했다.
Swagger vs Postman
1일 차 때 swagger에 대해서 배웠는데,
Postman도 사용해 봤다.

먼저 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 |
