티스토리 뷰

728x90
반응형

 

 

 

지난 글까지 JDBC 및 Spring Data JDBC의 소개와 데이터 연동을 위한 도메인 엔티티를 설계 및 구성했다.

 

이번 글에선 실제로 DB에 접근하는 Service 클래스와 Repository 인터페이스를 완성한다.

 

정확하게는 Service 클래스가 Entity를 관리하는 Repository 인터페이스를 이용해 DB에 접근하는 것이다.

 

 

 

Repository 인터페이스 구현

커피 주문을 위해서는 회원, 커피, 주문 정보가 모두 필요하기 때문에 각 레포지토리를 구현해야 한다.

 

  • MemberRepository
package com.gnidinger.member.repository;

import com.gnidinger.member.entity.Member;
import org.springframework.data.repository.CrudRepository;

import java.util.Optional;

public interface MemberRepository extends CrudRepository<Member, Long> { // (1)

    Optional<Member> findByEmail(String email); // (2)
}

(1) - MemberRepository 인터페이스가 CrudRepository 인터페이스를 상속

 

CrudRepository<>

 

별도의 설정 없이도 CRUD(Create, Read, Update, Delete) 작업을 할 수 있도록 지원하는 인터페이스이다.

 

내부적으로 Repository 인터페이스를 상속받고 있으며

 

<> 안의 Member는 Member 엔티티 클래스를, Long은 Member에서 @Id가 붙은 변수의 타입을 가리킨다.

 

 

 

 

(2) - 쿼리 메서드(Query Method) 정의를 이용한 데이터 조회 메서드 정의

 

쿼리 메서드(Query Method)

 

Spring Data JDBC에서는 메서드 이름으로 쿼리 생성을 하는 쿼리 메서드 기능을 제공한다.

 

조금 더 정확하게는 자동으로 메서드의 이름을 분석해서 SQL 쿼리를 실행하게 된다.

 

단순히 CrudRepository를 상속하는 것만으로도 이용할 수 있으며 사용 방법도 직관적이다.

 

CrudRepository를 상속받으며 타겟 클래스와 식별자를 지정했다면 양식에 맞춰 메서드 이름만 적으면 된다.

 

방법은 다음과 같다.

 

find + By + SQL 쿼리문에서 WHERE 절의 컬럼명 + (WHERE 절 컬럼의 조건이 되는 데이터)

 

주의할 점은 By 뒤에 붙는 컬럼명은 매핑되는 엔티티 클래스의 변수명으로 적어야 한다는 것이다.

 

예를 들어 우리 코드의 Optional<Member> findByEmail(String email); 은

 

EMAIL 컬럼을 조건으로 지정, MEMBER 테이블에서 하나의 row를 조회하겠다는 의미가 된다.

 

내부에선 위 코드를 아래와 같은 쿼리문으로 바꿔준다.

SELECT * FROM MEMBER
WHERE EMAIL = 'email';

 

'' 안의 email에 검증할 메일 주소를 입력하면 된다.

 

우리의 경우 회원가입 시 동일한 이메일 주소가 존재하는지 확인하는 용도로 쓰일 것이다.

 

추가로 두 개 이상의 정보를 조회하고 싶다면 메서드 이름에 AND를 붙여주면 된다.

findByEmailAndName(String email, String name)

 

Optional<>

 

 

Optional은 null이 올 수 있는 값을 감싸는 Wrapper 클래스로, 참조하더라도 NPE가 발생하지 않도록 도와준다.

 

아래에서는 Optional 클래스의 ofNullable() 메서드를 사용해 변수에 null 값을 허용한다.

 

  • CoffeeRepository
package com.gnidinger.coffee.repository;

import com.gnidinger.coffee.entity.Coffee;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.CrudRepository;

import java.util.Optional;

public interface CoffeeRepository extends CrudRepository<Coffee, Long> {

    Optional<Coffee> findByCoffeeCode(String coffeeCode);

    @Query("SELECT * FROM COFFEE WHERE COFFEE_ID = :coffeeId") // (1)
    Optional<Coffee> findByCoffee(Long coffeeId);
    
//    Optional<Coffee> findById(Long Id);
}

(1) - 쿼리 메서드가 아닌 @Query를 이용해 직접 쿼리문을 작성했다. 복잡한 쿼리 문의 경우 @Query를 사용한다.

 

실제로는 아래 주석처럼 Optional<Coffee> findById(Long Id);로 간단하게 처리하면 된다.

 

  • OrderRepository
package com.gnidinger.order.repository;

import com.gnidinger.order.entity.Order;
import org.springframework.data.repository.CrudRepository;

public interface OrderRepository extends CrudRepository<Order, Long> {
}

OrderRepository 인터페이스는 비어있지만 CrudRepository 인터페이스를 상속해

 

CrudRepository의 기본 쿼리 메서드를 서비스 클래스에서 사용할 수 있게 한다.

 

 

Service 클래스 구현

계속해서 각 서비스 클래스를 Spring Data JDBC 기술이 적용된 Repository 인터페이스를 사용하도록 수정한다.

 

  • MemberService
package com.gnidinger.member.service;

import com.gnidinger.exception.BusinessLogicException;
import com.gnidinger.exception.ExceptionCode;
import com.gnidinger.member.entity.Member;
import com.gnidinger.member.repository.MemberRepository;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class MemberService {
    private MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) { // (1) MemberRepository DI
        this.memberRepository = memberRepository;
    }

    public Member createMember(Member member) {

        verifyExistsEmail(member.getEmail()); // (2) 존재하는 이메일인지 검증

        return memberRepository.save(member); // (3) 회원 정보 저장
    }

    public Member updateMember(Member member) {

        Member findMember = findVerifiedMember(member.getMemberId()); // (4) 존재하는 회원인지 검증

        // (5) 이름 정보와 휴대폰 번호 정보 업데이트
        Optional.ofNullable(member.getName())
                .ifPresent(name -> findMember.setName(name));
        Optional.ofNullable(member.getPhone())
                .ifPresent(phone -> findMember.setPhone(phone));
        
        return memberRepository.save(findMember); // (6) 회원 정보 업데이트
    }
    
    public Member findMember(long memberId) { // (7) 회원 한 명 정보 조회
        return findVerifiedMember(memberId);
    }

    public List<Member> findMembers() { // (8) 모든 회원 정보 조회
        return (List<Member>) memberRepository.findAll();
    }

    public void deleteMember(long memberId) { // (9) 특정 회원 정보 삭제
        Member findMember = findVerifiedMember(memberId);        
        memberRepository.delete(findMember);
    }
    
    public Member findVerifiedMember(long memberId) { // (10) 존재하는 회원인지 검증
        Optional<Member> optionalMember =
                memberRepository.findById(memberId);
        Member findMember =
                optionalMember.orElseThrow(() ->
                        new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND));
        return findMember;
    }
    
    private void verifyExistsEmail(String email) { // (11) 존재하는 이메일 주소인지 검증
        Optional<Member> member = memberRepository.findByEmail(email);
        if (member.isPresent())
            throw new BusinessLogicException(ExceptionCode.MEMBER_EXISTS);
    }
}

(1) - 생성자를 통한 MemberRepository 인터페이스를 주입

 

MemberRepository의 구현 클래스

 

코드를 구현하면서 MemberRepository를 직접 구현하지 않았다.

 

Spring Data JDBC에선 내부적으로 Java의 리플렉션과 Proxy 기술을 이용해서

 

MemberRepository 인터페이스의 구현 클래스 객체를 자동으로 생성해 준다

 

 

(2) - 존재하는 이메일인지 검증하기 위해 (11) 사용

 

(3) - 검증이 끝나면 회원 정보 저장

 

(4) - 존재하는 회원인지 검증하기 위해 (10) 사용

 

(5) - 검증이 끝나면 회원 정보 수정(ofNullable() 사용)

 

(6) - 수정된 값을 테이블에 업데이트

 

회원정보 등록과 수정

 

Spring Data JDBC에서는 @Id가 추가된 엔티티 클래스의 변수 값이 0 또는 null이면 신규 데이터라고 판단,

 

테이블에 INSERT 쿼리를 전송하게 된다.

 

반면 변수의 값이 0 또는 null이 아니라면 이미 테이블에 존재하는 데이터라고 판단, 테이블에 UPDATE를 전송한다.

 

(7) - memberId에 해당하는 회원 조회

 

(8) - 테이블에 존재하는 모든 회원 조회

 

(9) - memberId에 해당하는 회원 삭제

 

(10) - 존재하는 회원인지 검증 후, 검증된 회원 정보 리턴

 

(11) - 존재하는 이메일인지 검증 후, 검증된 이메일 정보 리턴

 

  • CoffeeService
package com.gnidinger.coffee.service;

import com.gnidinger.coffee.repository.CoffeeRepository;
import com.gnidinger.coffee.entity.Coffee;
import com.gnidinger.exception.BusinessLogicException;
import com.gnidinger.exception.ExceptionCode;
import com.gnidinger.order.entity.Order;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
public class CoffeeService {
    private CoffeeRepository coffeeRepository;

    public CoffeeService(CoffeeRepository coffeeRepository) {
        this.coffeeRepository = coffeeRepository;
    }

    public Coffee createCoffee(Coffee coffee) {
        // (1) 커피 코드 대문자로 변경
        String coffeeCode = coffee.getCoffeeCode().toUpperCase();

        // 등록된 커피 코드인지 확인
        verifyExistCoffee(coffeeCode);
        coffee.setCoffeeCode(coffeeCode);

        return coffeeRepository.save(coffee);
    }

    public Coffee updateCoffee(Coffee coffee) {
        // 조회하려는 커피가 검증된 커피인지 확인(존재하는 커피인지 확인 등)
        Coffee findCoffee = findVerifiedCoffee(coffee.getCoffeeId());

        Optional.ofNullable(coffee.getKorName())
                .ifPresent(korName -> findCoffee.setKorName(korName));
        Optional.ofNullable(coffee.getEngName())
                .ifPresent(engName -> findCoffee.setEngName(engName));
        Optional.ofNullable(coffee.getPrice())
                .ifPresent(price -> findCoffee.setPrice(price));

        return coffeeRepository.save(findCoffee);
    }

    public Coffee findCoffee(long coffeeId) {
        return findVerifiedCoffeeByQuery(coffeeId);
    }

    // (2) 주문에 해당하는 커피 정보 조회
    public List<Coffee> findOrderedCoffees(Order order) {
        return order.getOrderCoffees()
                .stream()
                .map(coffeeRef -> findCoffee(coffeeRef.getCoffeeId()))
                .collect(Collectors.toList());
    }

    public List<Coffee> findCoffees() {
        return (List<Coffee>) coffeeRepository.findAll();
    }

    public void deleteCoffee(long coffeeId) {
        Coffee coffee = findVerifiedCoffee(coffeeId);
        coffeeRepository.delete(coffee);
    }

    public Coffee findVerifiedCoffee(long coffeeId) {
        Optional<Coffee> optionalCoffee = coffeeRepository.findById(coffeeId);
        Coffee findCoffee =
                optionalCoffee.orElseThrow(() ->
                        new BusinessLogicException(ExceptionCode.COFFEE_NOT_FOUND));

        return findCoffee;
    }

    private void verifyExistCoffee(String coffeeCode) {
        Optional<Coffee> coffee = coffeeRepository.findByCoffeeCode(coffeeCode);
        if (coffee.isPresent())
            throw new BusinessLogicException(ExceptionCode.COFFEE_CODE_EXISTS);
    }

    private Coffee findVerifiedCoffeeByQuery(long coffeeId) {
        Optional<Coffee> optionalCoffee = coffeeRepository.findByCoffee(coffeeId);
        Coffee findCoffee =
                optionalCoffee.orElseThrow(() ->
                        new BusinessLogicException(ExceptionCode.COFFEE_NOT_FOUND));

        return findCoffee;
    }
}

(1) - 커피 코드를 대문자로 변경. 사용자가 대소문자에 신경 쓰지 않고 입력할 수 있도록 함

 

(2) - 커피 정보 조회. getOrderCoffees()를 사용해 구체적인 커피 주문 정보 받아옴

 

  • OrderService
package com.gnidinger.order.service;

import com.gnidinger.coffee.service.CoffeeService;
import com.gnidinger.exception.BusinessLogicException;
import com.gnidinger.exception.ExceptionCode;
import com.gnidinger.member.service.MemberService;
import com.gnidinger.order.entity.Order;
import com.gnidinger.order.repository.OrderRepository;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class OrderService {
    final private OrderRepository orderRepository;
    final private MemberService memberService;
    final private CoffeeService coffeeService;

    public OrderService(OrderRepository orderRepository,
                        MemberService memberService,
                        CoffeeService coffeeService) {
        this.orderRepository = orderRepository;
        this.memberService = memberService;
        this.coffeeService = coffeeService;
    }

    public Order createOrder(Order order) {
        // (1) 존재하는 회원인지 확인
        memberService.findVerifiedMember(order.getMemberId().getId());

        // (2) 존재하는 커피인지 조회
        order.getOrderCoffees()
                .stream()
                .forEach(coffeeRef -> {
                    coffeeService.findVerifiedCoffee(coffeeRef.getCoffeeId());
                });
        return orderRepository.save(order);
    }

    public Order findOrder(long orderId) {
        return findVerifiedOrder(orderId);
    }

    public List<Order> findOrders() {
        return (List<Order>) orderRepository.findAll();
    }

    // (3) 주문 취소 요청
    public void cancelOrder(long orderId) {
        Order findOrder = findVerifiedOrder(orderId);
        int step = findOrder.getOrderStatus().getStepNumber();

        // (4) OrderStatus의 step이 2미만일 경우(ORDER_CONFIRM)에만 주문취소 가능
        if (step >= 2) {
            throw new BusinessLogicException(ExceptionCode.CANNOT_CHANGE_ORDER);
        }

        // (5) (4)의 조건을 만족하면 취소
        findOrder.setOrderStatus(Order.OrderStatus.ORDER_CANCEL);
        orderRepository.save(findOrder);
    }

    private Order findVerifiedOrder(long orderId) {
        Optional<Order> optionalOrder = orderRepository.findById(orderId);
        Order findOrder =
                optionalOrder.orElseThrow(() ->
                        new BusinessLogicException(ExceptionCode.ORDER_NOT_FOUND));
        return findOrder;
    }
}

겹치는 설명은 생략한다.

 

 

추가로 수정된 코드

 

  • CoffeePostDto
package com.gnidinger.coffee.dto;

import lombok.Getter;
import org.hibernate.validator.constraints.Range;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

@Getter
public class CoffeePostDto {
    @NotBlank
    private String korName;

    @NotBlank
    @Pattern(regexp = "^([A-Za-z])(\\s?[A-Za-z])*$",
            message = "커피명(영문)은 영문이어야 합니다(단어 사이 공백 한 칸 포함). 예) Cafe Latte")
    private String engName;

    @Range(min= 100, max= 50000)
    private int price;

    @NotBlank
    @Pattern(regexp = "^([A-Za-z]){3}$",
            message = "커피 코드는 3자리 영문이어야 합니다.")
    private String coffeeCode; // 커피 코드 추가
}

 

  • OrderController
package com.gnidinger.order.controller;

import com.gnidinger.coffee.service.CoffeeService;
import com.gnidinger.order.entity.Order;
import com.gnidinger.order.service.OrderService;
import com.gnidinger.order.dto.OrderPostDto;
import com.gnidinger.order.dto.OrderResponseDto;
import com.gnidinger.order.mapper.OrderMapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.Positive;
import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/v7/orders")
@Validated
public class OrderController {
    private final OrderService orderService;
    private final OrderMapper mapper;
    private final CoffeeService coffeeService;

    public OrderController(OrderService orderService,
                           OrderMapper mapper,
                           CoffeeService coffeeService) {
        this.orderService = orderService;
        this.mapper = mapper;
        this.coffeeService = coffeeService;
    }

    @PostMapping
    public ResponseEntity postOrder(@Valid @RequestBody OrderPostDto orderPostDto) {
        Order order =
                orderService.createOrder(mapper.orderPostDtoToOrder(orderPostDto));

        // (1) 주문한 커피 정보를 OrderResponseDto에 포함시키도록 수정
        return new ResponseEntity<>(mapper.orderToOrderResponseDto(coffeeService, order), HttpStatus.CREATED);
    }

    @GetMapping("/{order-id}")
    public ResponseEntity getOrder(@PathVariable("order-id") @Positive long orderId){
        Order order = orderService.findOrder(orderId);

        // (2) 주문한 커피 정보를 OrderResponseDto에 포함시키도록 수정
        return new ResponseEntity<>(mapper.orderToOrderResponseDto(coffeeService, order), HttpStatus.OK);
    }

    @GetMapping
    public ResponseEntity getOrders() {
        List<Order> orders = orderService.findOrders();

        // (3) 주문한 커피 정보를 OrderResponseDto에 포함시키도록 수정
        List<OrderResponseDto> response =
                orders.stream().map(order -> 
                        mapper.orderToOrderResponseDto(coffeeService, order)).collect(Collectors.toList());

        return new ResponseEntity<>(response, HttpStatus.OK);
    }

    @DeleteMapping("/{order-id}")
    public ResponseEntity cancelOrder(@PathVariable("order-id") @Positive long orderId){
        orderService.cancelOrder(orderId);
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
}

 

  • OrderPostDto
package com.gnidinger.order.dto;

import lombok.Getter;

import javax.validation.Valid;
import javax.validation.constraints.Positive;
import java.util.List;

@Getter
public class OrderPostDto {
    @Positive
    private long memberId;

    // 여러 잔의 커피를 주문할 수 있도록 수정
    @Valid
    private List<OrderCoffeeDto> orderCoffees;
}

 

  • OrderCoffeeDto 생성
package com.gnidinger.order.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

import javax.validation.constraints.Positive;

@Getter
@AllArgsConstructor
public class OrderCoffeeDto { // 여러 잔의 커피 정보를 주문하기 위해 추가
    @Positive
    private long coffeeId;

    @Positive
    private int quantity;
}

 

  • OrderCoffeeResponseDto 생성
package com.gnidinger.order.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class OrderCoffeeResponseDto { // 여러 잔의 커피 정보를 응답으로 돌려주기 위해 추가
    private long coffeeId;
    private String korName;
    private String engName;
    private int price;
    private int quantity;
}

 

  • OrderResponseDto
package com.gnidinger.order.dto;

import com.gnidinger.coffee.dto.CoffeeResponseDto;
import com.gnidinger.order.entity.Order;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.List;

@Getter
@Setter
public class OrderResponseDto {
    private long orderId;
    private long memberId;
    private Order.OrderStatus orderStatus; // 주문 상태 응답으로 전송
    private List<OrderCoffeeResponseDto> orderCoffees; // 여러 잔의 커피 정보 응답으로 전송 
    private LocalDateTime createdAt; // 주문 시간 응답으로 전송
}

 

  • OrderMapper
package com.gnidinger.order.mapper;

import com.gnidinger.coffee.entity.Coffee;
import com.gnidinger.order.entity.CoffeeRef;
import com.gnidinger.coffee.service.CoffeeService;
import com.gnidinger.order.dto.OrderCoffeeResponseDto;
import com.gnidinger.order.entity.Order;
import com.gnidinger.order.dto.OrderPostDto;
import com.gnidinger.order.dto.OrderResponseDto;
import org.mapstruct.Mapper;
import org.springframework.data.jdbc.core.mapping.AggregateReference;

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

@Mapper(componentModel = "spring")
public interface OrderMapper {
    // 수정되었음.
    default Order orderPostDtoToOrder(OrderPostDto orderPostDto) {
        Order order = new Order();
        order.setMemberId(
                new AggregateReference.IdOnlyAggregateReference(orderPostDto.getMemberId()));
        Set<CoffeeRef> orderCoffees = orderPostDto.getOrderCoffees()
                .stream()
                .map(orderCoffeeDto -> new CoffeeRef(orderCoffeeDto.getCoffeeId(),
                        orderCoffeeDto.getQuantity()))
                .collect(Collectors.toSet());
        order.setOrderCoffees(orderCoffees);

        return order;
    }

    default OrderResponseDto orderToOrderResponseDto(CoffeeService coffeeService,
                                                     Order order) {

        long memberId = order.getMemberId().getId();

        List<OrderCoffeeResponseDto> orderCoffees =
                orderToOrderCoffeeResponseDto(coffeeService, order.getOrderCoffees());

        OrderResponseDto orderResponseDto = new OrderResponseDto();
        orderResponseDto.setOrderCoffees(orderCoffees);
        orderResponseDto.setMemberId(memberId);
        orderResponseDto.setCreatedAt(order.getCreatedAt());
        orderResponseDto.setOrderId(order.getOrderId());
        orderResponseDto.setOrderStatus(order.getOrderStatus());

        return orderResponseDto;
    }

    default List<OrderCoffeeResponseDto> orderToOrderCoffeeResponseDto(
            CoffeeService coffeeService,
            Set<CoffeeRef> orderCoffees) {
        return orderCoffees.stream()
                .map(coffeeRef -> {
                    Coffee coffee = coffeeService.findCoffee(coffeeRef.getCoffeeId());

                    return new OrderCoffeeResponseDto(coffee.getCoffeeId(),
                            coffee.getKorName(),
                            coffee.getEngName(),
                            coffee.getPrice(),
                            coffeeRef.getQuantity());
                }).collect(Collectors.toList());
    }
}

기존의 MapStruct를 통해 자동으로 엔티티 클래스와 DTO 클래스를 매핑하던 방식에서

 

개발자가 직접 Mapper 클래스를 작성하는 방식으로 변경되었다.

 

  • ExceptionCode
package com.gnidinger.exception;

import lombok.Getter;

public enum ExceptionCode {
    MEMBER_NOT_FOUND(404, "Member not found"),
    MEMBER_EXISTS(409, "Member exists"),
    COFFEE_NOT_FOUND(404, "Coffee not found"),
    COFFEE_CODE_EXISTS(409, "Coffee Code exists"),
    ORDER_NOT_FOUND(404, "Order not found"),
    CANNOT_CHANGE_ORDER(403, "Order can not change"),
    NOT_IMPLEMENTATION(501, "Not Implementation");

    @Getter
    private int status;

    @Getter
    private String message;

    ExceptionCode(int code, String message) {
        this.status = code;
        this.message = message;
    }
}

 

실행 테스트

 

Postman을 통해 회원과 커피를 등록하고 주문을 넣어보자.

{
    "email": "gni@gmail.com",
    "name": "gnidinger",
    "phone": "010-1234-5678"
}

위와 같은 회원 정보를 등록하고

{
    "korName": "아이스 아메리카노",
    "engName": "Iced Americano",
    "price": 3000, 
    "coffeeCode": "ICA"
}
{
    "korName": "바닐라 라떼",
    "engName": "Vanilla Latte",
    "price": 5000, 
    "coffeeCode": "VNL"
}

위와 같이 커피 정보를 입력한 뒤

{
    "memberId": 1,
    "orderCoffees":[
        {
            "coffeeId": 1,
            "quantity": 2
        },
        {
            "coffeeId": 2,
            "quantity": 1
        }
    ]
}

/orders에게 위와 같은 POST 요청을 보내면, 다음과 같이 응답이 온다.

{
    "orderId": 1,
    "memberId": 1,
    "orderStatus": "ORDER_REQUEST",
    "orderCoffees": [
        {
            "coffeeId": 1,
            "korName": "아이스 아메리카노",
            "engName": "Iced Americano",
            "price": 3000,
            "quantity": 2
        },
        {
            "coffeeId": 2,
            "korName": "바닐라 라떼",
            "engName": "Vanilla Latte",
            "price": 5000,
            "quantity": 1
        }
    ],
    "createdAt": "2022-08-30T20:10:02.212316"

위와 비슷한 응답이 왔다면 성공한 것이다.

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   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
글 보관함