[Spring][쇼핑몰 프로젝트][42] 주문 구현 - 3
프로젝트 Github : https://github.com/sjinjin7/Blog_Project
프로젝트 포스팅 색인(index) : https://kimvampa.tistory.com/188
목표
주문 구현 - 서버 구현
"주문" 처리를 하는 핵심 비즈니스 로직을 수행하는 Service 메서드 작성을 하고 Controller에서 작성한 Service 메서드를 호출하여 "주문"처리 작업을 마무리합니다.
순서
1. 주문 Service 메서드 개요
2. OrderService
3. OrderController
4. 테스트
1. 주문 Service 메서드 개요
이전 포스팅에서 만든 Mapper 메서드를 활용하여 "주문" 처리를 하는 Service 메서드를 만들 것입니다.
본격적인 코드에 앞서서 먼저 "주문 처리" service 메서드는 다음과 같은 작업을 처리하도록 구현할 것입니다.
- 주문 데이터를 DB에 등록해주어야 한다.
- 주문 상품의 비용과 포인트(사용 포인트&받을 포인트) 회원 정보의 돈과 포인트에서 차감하거나 더해 주어야 한다.
- 주문한 상품 수만큼 상품의 재고를 차감해주어야 합니다.
- 회원이 장바구니 경로를 통해 주문을 한 경우, 주문이 이루어진 장바구니 상품 정보를 제거해주어야 한다.
Service 메서드의 로직은 아래와 같은 순으로 구성할 것입니다.
1. 사용할 데이터를 세팅 (회원 객체, 주문 객체)
2. 주문 데이터 DB에 등록
3. 비용, 포인트 변동 DB 적용
4. 재고 차감 DB 적용
5. 장바구니 상품 정보 DB 제거
2. OrderService
OrderService
OrderService 인터페이스에 '주문 처리'를 하는 메서드의 선언부를 추가해줍니다.
/* 주문 */
public void order(OrderRequestWrapper orw);
OrderServiceImpl
인터페이스에서 선언한 메서드를 오버라이딩 합니다.
@Override
public void order(OrderDTO orw) {
}
현재 작업할 Service 메서드는 여러 쿼리를 처리하게 되는데 하나의 단위로 처리가 되도록 @Transactional 어노테이션을 추가해줍니다.
CartMapper, MemberMapper의 메서드를 사용할 것이기 때문에 두 개의 객체를 의존성 주입해줍니다.
@Autowired
private MemberMapper memberMapper;
@Autowired
private CartMapper cartMapper;
@Autowired
private BookMapper bookMapper;
이제 메서드의 구현부를 작성해보겠습니다.
사용할 데이터 세팅
주문자의 정보가 담긴 객체를 세팅합니다.
/* 사용할 데이터가져오기 */
/* 회원 정보 */
MemberVO member = memberMapper.getMemberInfo(ord.getMemberId());
주문 정보가 담긴 객체(OrderDTO, OrderItemDTO)를 세팅합니다.
/* 사용할 데이터가져오기 */
/* 회원 정보 */
MemberVO member = memberMapper.getMemberInfo(ord.getMemberId());
/* 주문 정보 */
List<OrderItemDTO> ords = new ArrayList<>();
for(OrderItemDTO oit : ord.getOrders()) {
OrderItemDTO orderItem = orderMapper.getOrderInfo(oit.getBookId());
// 수량 셋팅
orderItem.setBookCount(oit.getBookCount());
// 기본정보 셋팅
orderItem.initSaleTotal();
//List객체 추가
ords.add(orderItem);
}
/* OrderDTO 셋팅 */
ord.setOrders(ords);
ord.getOrderPriceInfo();
주문 데이터 DB 등록
현시점에서의 주문 객체로 DB 등록하는 Mapper 메서드를 실행할 경우 에러가 납니다. 왜냐하면 vam_order 테이블에 등록되는 orderId 속성은 DB의 자동생성 기능을 사용하지 않아 우리가 직접 부여해주어야 하는데 OrderDTO의 orderId에 아직 값을 부여해주지 않았기 때문입니다.
따라서 orderId를 만들고 OrderDTO 객체의 orderId에 값을 부여해주는 코드를 추가합니다. orderId는 "회원 아이디" + "_년도 월 일 분" 형태로 가지도록 SimpleDateFormat 객체를 활용하여 만들었습니다.
※ SimpeDateFormat은 이전 이미지 작업에서 사용하기도 했고 구글링 하면 많은 자료를 찾을 수 있기에 설명을 생략합니다.
/* 사용할 데이터가져오기 */
/* 회원 정보 */
MemberVO member = memberMapper.getMemberInfo(ord.getMemberId());
/* 주문 정보 */
List<OrderItemDTO> ords = new ArrayList<>();
for(OrderItemDTO oit : ord.getOrders()) {
OrderItemDTO orderItem = orderMapper.getOrderInfo(oit.getBookId());
// 수량 셋팅
orderItem.setBookCount(oit.getBookCount());
// 기본정보 셋팅
orderItem.initSaleTotal();
//List객체 추가
ords.add(orderItem);
}
/* OrderDTO 셋팅 */
ord.setOrders(ords);
ord.getOrderPriceInfo();
/*DB 주문,주문상품(,배송정보) 넣기*/
/* orderId만들기 및 OrderDTO객체 orderId에 저장 */
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("_yyyyMMddmm");
String orderId = member.getMemberId() + format.format(date);
ord.setOrderId(orderId);
세팅된 주문 객체의 데이터를 vam_order, vam_orderItem 테이블에 등록하는 Mapper 메서드를 호출합니다.
/* 사용할 데이터가져오기 */
/* 회원 정보 */
MemberVO member = memberMapper.getMemberInfo(ord.getMemberId());
/* 주문 정보 */
List<OrderItemDTO> ords = new ArrayList<>();
for(OrderItemDTO oit : ord.getOrders()) {
OrderItemDTO orderItem = orderMapper.getOrderInfo(oit.getBookId());
// 수량 셋팅
orderItem.setBookCount(oit.getBookCount());
// 기본정보 셋팅
orderItem.initSaleTotal();
//List객체 추가
ords.add(orderItem);
}
/* OrderDTO 셋팅 */
ord.setOrders(ords);
ord.getOrderPriceInfo();
/*DB 주문,주문상품(,배송정보) 넣기*/
/* orderId만들기 및 OrderDTO객체 orderId에 저장 */
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("_yyyyMMddmm");
String orderId = member.getMemberId() + format.format(date);
ord.setOrderId(orderId);
/* db넣기 */
orderMapper.enrollOrder(ord); //vam_order 등록
for(OrderItemDTO oit : ord.getOrders()) { //vam_orderItem 등록
oit.setOrderId(orderId);
orderMapper.enrollOrderItem(oit);
}
비용, 포인트 변동 DB 적용
회원 객체의 돈(money)에서 주문 상품 비용을 차감한 값을 회원객체의 돈 변수에 저장해줍니다.
/* 사용할 데이터가져오기 */
/* 회원 정보 */
MemberVO member = memberMapper.getMemberInfo(ord.getMemberId());
/* 주문 정보 */
List<OrderItemDTO> ords = new ArrayList<>();
for(OrderItemDTO oit : ord.getOrders()) {
OrderItemDTO orderItem = orderMapper.getOrderInfo(oit.getBookId());
// 수량 셋팅
orderItem.setBookCount(oit.getBookCount());
// 기본정보 셋팅
orderItem.initSaleTotal();
//List객체 추가
ords.add(orderItem);
}
/* OrderDTO 셋팅 */
ord.setOrders(ords);
ord.getOrderPriceInfo();
/*DB 주문,주문상품(,배송정보) 넣기*/
/* orderId만들기 및 OrderDTO객체 orderId에 저장 */
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("_yyyyMMddmm");
String orderId = member.getMemberId() + format.format(date);
ord.setOrderId(orderId);
/* db넣기 */
orderMapper.enrollOrder(ord); //vam_order 등록
for(OrderItemDTO oit : ord.getOrders()) { //vam_orderItem 등록
oit.setOrderId(orderId);
orderMapper.enrollOrderItem(oit);
}
/* 비용 포인트 변동 적용 */
/* 비용 차감 & 변동 돈(money) Member객체 적용 */
int calMoney = member.getMoney();
calMoney -= ord.getOrderFinalSalePrice();
member.setMoney(calMoney);
회원객체의 포인트(point)에서 상품 주문에서 사용한 포인트를 차감하고 주문으로 인해 획득할 포인트를 가산해준 값을 회원객체 포인트 변수에 저장해줍니다.
/* 사용할 데이터가져오기 */
/* 회원 정보 */
MemberVO member = memberMapper.getMemberInfo(ord.getMemberId());
/* 주문 정보 */
List<OrderItemDTO> ords = new ArrayList<>();
for(OrderItemDTO oit : ord.getOrders()) {
OrderItemDTO orderItem = orderMapper.getOrderInfo(oit.getBookId());
// 수량 셋팅
orderItem.setBookCount(oit.getBookCount());
// 기본정보 셋팅
orderItem.initSaleTotal();
//List객체 추가
ords.add(orderItem);
}
/* OrderDTO 셋팅 */
ord.setOrders(ords);
ord.getOrderPriceInfo();
/*DB 주문,주문상품(,배송정보) 넣기*/
/* orderId만들기 및 OrderDTO객체 orderId에 저장 */
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("_yyyyMMddmm");
String orderId = member.getMemberId() + format.format(date);
ord.setOrderId(orderId);
/* db넣기 */
orderMapper.enrollOrder(ord); //vam_order 등록
for(OrderItemDTO oit : ord.getOrders()) { //vam_orderItem 등록
oit.setOrderId(orderId);
orderMapper.enrollOrderItem(oit);
}
/* 비용 포인트 변동 적용 */
/* 비용 차감 & 변동 돈(money) Member객체 적용 */
int calMoney = member.getMoney();
calMoney -= ord.getOrderFinalSalePrice();
member.setMoney(calMoney);
/* 포인트 차감, 포인트 증가 & 변동 포인트(point) Member객체 적용 */
int calPoint = member.getPoint();
calPoint = calPoint - ord.getUsePoint() + ord.getOrderSavePoint(); // 기존 포인트 - 사용 포인트 + 획득 포인트
member.setPoint(calPoint);
변경된 돈, 포인트 데이터를 DB에 반영해주는 deductMoney 메서드를 호출합니다.
/* 사용할 데이터가져오기 */
/* 회원 정보 */
MemberVO member = memberMapper.getMemberInfo(ord.getMemberId());
/* 주문 정보 */
List<OrderItemDTO> ords = new ArrayList<>();
for(OrderItemDTO oit : ord.getOrders()) {
OrderItemDTO orderItem = orderMapper.getOrderInfo(oit.getBookId());
// 수량 셋팅
orderItem.setBookCount(oit.getBookCount());
// 기본정보 셋팅
orderItem.initSaleTotal();
//List객체 추가
ords.add(orderItem);
}
/* OrderDTO 셋팅 */
ord.setOrders(ords);
ord.getOrderPriceInfo();
/*DB 주문,주문상품(,배송정보) 넣기*/
/* orderId만들기 및 OrderDTO객체 orderId에 저장 */
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("_yyyyMMddmm");
String orderId = member.getMemberId() + format.format(date);
ord.setOrderId(orderId);
/* db넣기 */
orderMapper.enrollOrder(ord); //vam_order 등록
for(OrderItemDTO oit : ord.getOrders()) { //vam_orderItem 등록
oit.setOrderId(orderId);
orderMapper.enrollOrderItem(oit);
}
/* 비용 포인트 변동 적용 */
/* 비용 차감 & 변동 돈(money) Member객체 적용 */
int calMoney = member.getMoney();
calMoney -= ord.getOrderFinalSalePrice();
member.setMoney(calMoney);
/* 포인트 차감, 포인트 증가 & 변동 포인트(point) Member객체 적용 */
int calPoint = member.getPoint();
calPoint = calPoint - ord.getUsePoint() + ord.getOrderSavePoint(); // 기존 포인트 - 사용 포인트 + 획득 포인트
member.setPoint(calPoint);
/* 변동 돈, 포인트 DB 적용 */
orderMapper.deductMoney(member);
재고 차감 DB 적용
상품의 재고 값을 가져와 그 값에서 주문한 상품의 수만큼 차감한 값을 DB에 반영하는 코드를 추가합니다.
/* 사용할 데이터가져오기 */
/* 회원 정보 */
MemberVO member = memberMapper.getMemberInfo(ord.getMemberId());
/* 주문 정보 */
List<OrderItemDTO> ords = new ArrayList<>();
for(OrderItemDTO oit : ord.getOrders()) {
OrderItemDTO orderItem = orderMapper.getOrderInfo(oit.getBookId());
// 수량 셋팅
orderItem.setBookCount(oit.getBookCount());
// 기본정보 셋팅
orderItem.initSaleTotal();
//List객체 추가
ords.add(orderItem);
}
/* OrderDTO 셋팅 */
ord.setOrders(ords);
ord.getOrderPriceInfo();
/*DB 주문,주문상품(,배송정보) 넣기*/
/* orderId만들기 및 OrderDTO객체 orderId에 저장 */
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("_yyyyMMddmm");
String orderId = member.getMemberId() + format.format(date);
ord.setOrderId(orderId);
/* db넣기 */
orderMapper.enrollOrder(ord); //vam_order 등록
for(OrderItemDTO oit : ord.getOrders()) { //vam_orderItem 등록
oit.setOrderId(orderId);
orderMapper.enrollOrderItem(oit);
}
/* 비용 포인트 변동 적용 */
/* 비용 차감 & 변동 돈(money) Member객체 적용 */
int calMoney = member.getMoney();
calMoney -= ord.getOrderFinalSalePrice();
member.setMoney(calMoney);
/* 포인트 차감, 포인트 증가 & 변동 포인트(point) Member객체 적용 */
int calPoint = member.getPoint();
calPoint = calPoint - ord.getUsePoint() + ord.getOrderSavePoint(); // 기존 포인트 - 사용 포인트 + 획득 포인트
member.setPoint(calPoint);
/* 변동 돈, 포인트 DB 적용 */
orderMapper.deductMoney(member);
/* 재고 변동 적용 */
for(OrderItemDTO oit : ord.getOrders()) {
/* 변동 재고 값 구하기 */
BookVO book = bookMapper.getGoodsInfo(oit.getBookId());
book.setBookStock(book.getBookStock() - oit.getBookCount());
/* 변동 값 DB 적용 */
orderMapper.deductStock(book);
}
장바구니 상품 정보 DB 제거
장바구니 상품 정보를 제거하는 Mapper 메서드를 호출합니다. memberId와 bookId를 조건으로 vam_cart의 행을 제거하는 쿼리가 실행이 됩니다. 만약 조건을 충족하는 vam_cart의 행이 존재한다면 삭제될 것입니다.
/* 사용할 데이터가져오기 */
/* 회원 정보 */
MemberVO member = memberMapper.getMemberInfo(ord.getMemberId());
/* 주문 정보 */
List<OrderItemDTO> ords = new ArrayList<>();
for(OrderItemDTO oit : ord.getOrders()) {
OrderItemDTO orderItem = orderMapper.getOrderInfo(oit.getBookId());
// 수량 셋팅
orderItem.setBookCount(oit.getBookCount());
// 기본정보 셋팅
orderItem.initSaleTotal();
//List객체 추가
ords.add(orderItem);
}
/* OrderDTO 셋팅 */
ord.setOrders(ords);
ord.getOrderPriceInfo();
/*DB 주문,주문상품(,배송정보) 넣기*/
/* orderId만들기 및 OrderDTO객체 orderId에 저장 */
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("_yyyyMMddmm");
String orderId = member.getMemberId() + format.format(date);
ord.setOrderId(orderId);
/* db넣기 */
orderMapper.enrollOrder(ord); //vam_order 등록
for(OrderItemDTO oit : ord.getOrders()) { //vam_orderItem 등록
oit.setOrderId(orderId);
orderMapper.enrollOrderItem(oit);
}
/* 비용 포인트 변동 적용 */
/* 비용 차감 & 변동 돈(money) Member객체 적용 */
int calMoney = member.getMoney();
calMoney -= ord.getOrderFinalSalePrice();
member.setMoney(calMoney);
/* 포인트 차감, 포인트 증가 & 변동 포인트(point) Member객체 적용 */
int calPoint = member.getPoint();
calPoint = calPoint - ord.getUsePoint() + ord.getOrderSavePoint(); // 기존 포인트 - 사용 포인트 + 획득 포인트
member.setPoint(calPoint);
/* 변동 돈, 포인트 DB 적용 */
orderMapper.deductMoney(member);
/* 재고 변동 적용 */
for(OrderItemDTO oit : ord.getOrders()) {
/* 변동 재고 값 구하기 */
BookVO book = bookMapper.getGoodsInfo(oit.getBookId());
book.setBookStock(book.getBookStock() - oit.getBookCount());
/* 변동 값 DB 적용 */
orderMapper.deductStock(book);
}
/* 장바구니 제거 */
for(OrderItemDTO oit : ord.getOrders()) {
CartDTO dto = new CartDTO();
dto.setMemberId(ord.getMemberId());
dto.setBookId(oit.getBookId());
cartMapper.deleteOrderCart(dto);
}
3. OrderController
이전에 작성해둔 "주문"요청을 수행하는 URL 매핑 메서드의 구현부에 앞서 작성한 Service 메서드를 호출합니다.
orderService.order(od);
Controller에서 한 가지 더 해주어야 할 것이 있는데 회원 정보 session인 "member"를 최신화해주어야 합니다. 홈페이지 오른쪽 상단에 보면 회원의 정보가 출력되도록 해놓았습니다.
이 섹션의 정보는 회원 정보 session인 "member" 데이터가 출력이 되는데 변동된 데이터(돈, 포인트)를 적용해주지 않는다면 주문하기 전 돈, 포인트 정보가 출력됩니다.
따라서 아래의 코드를 추가하여 회원 정보 session인 "member"에 변동 값이 적용된 MemberVO 객체로 바꿔줍니다. 회원 정보를 가져오는 service 메서드는 로그인 때 사용하던 Service 메서드를 그대로 사용하였습니다. try/catch문을 사용한 이유는 memberLogin 메서드에 thow 키워드를 추가해주었기 때문에 사용하였습니다.)
HttpSession session = request.getSession();
try {
MemberVO memberLogin = memberService.memberLogin(member);
memberLogin.setMemberPw("");
session.setAttribute("member", memberLogin);
} catch (Exception e) {
e.printStackTrace();
}
4. 테스트
"상품 상세 페이지"에서 '바로 구매' 버튼을 통한 주문, "장바구니 페이지"에서 '주문하기' 버튼을 통한 주문이 정상적으로 처리되었는지 확인합니다.
확인해야 할 상황은 회원의 '돈', '포인트'가 정상적으로 차감되었는지, 상품의 재고가 정상적으로 차감되었는지, 주문 정보가 테이블에 등록되었는지, (장바구니를 통해 주문을 한경우) 장바구니 정보가 제거되었는지를 확인해야 합니다.
* 변경 전
- 충전 금액 : 500,000
- 포인트 : 10,000
* 변경 후
- 변동 금액 : 500,000(기존) - 15,400(상품 금액) + 1,500(사용포인트) = 483,100
- 변동 포인트 : 10,000(기존) - 1,500(사용포인트) + 770(획득포인트) = 9,270
5. 주문 작업 마치며
주문 기능을 완성 하긴 했지만, 현재의 "주문 Service" 코드는 여러 가지 문제가 있습니다. 당장 생각할 수 있는 예외 상황은 회원이 DB에 등록된 상품 수 보다 더 많은 수의 상품을 주문하게 된다면 예외 상황이 발생할 것입니다. 이러한 예외 상황에 대응할 수 있도록 코드의 추가 및 수정이 필요로 합니다. 하지만 이러한 처리까지 모두 포스팅하게 되면 "주문"에 관한 글이 너무 길어져서 추가 작업에 관한 글은 더 이상 진행하지 않습니다. 핵심적인 "주문"처리의 흐름은 잡았기 때문에 예외 상황 혹은 더 나은 코드로 직접 구현해보셨으면 합니다.
※ 저라면 예외가 예상되는 Mapper 코드에 try/catch, Service 메서드 선언부에 throws 키워드를 추가하여 Service 메서드를 호출하는 쪽으로 예외를 던지도록 할 것입니다. 그리고 Controller에서는 Service를 호출할 때 try/catch를 사용하여 발생하는 예외 상황에 따라 에러 페이지로 이동을 시켜서 주문이 실패했음을 알린다던지와 같은 식으로 구현 할 거 같습니다.
REFERENCE
DATE
- 2020.12.21