Team project - 쇼핑몰 구현(5)
2024. 4. 29. 16:18ㆍ개발일지
마지막 주문 페이지다.
프로젝트 발표가 끝난 후 수업이 종료되어,
cart를 미 해결 상태로 남겨둔 채 프로젝트가 마무리 되었다.
너무 찝찝했다.
책이랑 블로그를 찾아서 완성해 보려고 일단 시도했다.
혼자서 하려니 힘이들었지만 구현이 되어 너무너무 기쁘다'-'
일단은 간단하게 주문 현황을 알 수 있도록 구현하였고, 나중에 결제 방법을 추가하고 아임포트도 추가해 볼 생각이다.
(가능하다면..........ㅋㅋ)
OrderDTO
/*
작성자 : 정아름
작성일 : 24.04.12
작성내용 : 주문 구현
확인사항 :
*/
package com.example.basic.DTO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class OrderDTO {
//주문 번호
private Integer orderId;
//회원 번호
private Integer memberId;
//등록일
private LocalDateTime regDate;
//수정일
private LocalDateTime modDate;
//추가사항
//주문 상품 번호
private Integer orderItemId;
}
OrderItemDTO
/*
작성자 : 정아름
작성일 : 24.04.12
작성내용 : 주문
확인사항 : 테스트 완료
*/
package com.example.basic.DTO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class OrderItemDTO {
//주문 상품 번호
private Integer orderItemId;
//주문 상품 수량
private Integer quantity;
//주문 상품 금액
private Integer orderPrice;
//등록일
private LocalDateTime regDate;
//수정일
private LocalDateTime modDate;
//주문 상태
private boolean orderStatus;
//추가사항
//주문 번호
private Integer orderId;
//상품 번호
private Integer productId;
//상품 이름
private String productName;
//상품 이미지
private String productImage;
//상품 가격
private Integer productPrice;
}
OrderEntity
package com.example.basic.Entity;
/*
작성자 : 정아름
작성일 : 24.04.12
수정사항 :
*/
import jakarta.persistence.*;
import lombok.*;
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Builder
@Table(name = "orders")
@SequenceGenerator(name = "oders_SEQ", sequenceName = "oders_SEQ", initialValue = 1,
allocationSize = 1)
public class OrderEntity extends BaseEntity {
//주문 번호
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "oders_SEQ")
private Integer orderId;
//다대일(N:1)관계를 정의
//여러개의 주문이 하나의 회원 또는 상품에 해당할 수 있다.
@ManyToOne(fetch = FetchType.LAZY)
//외래키 정의
//주문 테이블은 회원 테이블과 연관되어 있다.
@JoinColumn(name = "member_id", nullable = false)
private MemberEntity memberEntity;
}
OrderItemEntity
package com.example.basic.Entity;
/*
작성자 : 정아름
작성일 : 24.04.12
수정사항 :
*/
import jakarta.persistence.*;
import lombok.*;
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Builder
@Table(name = "orderitem")
@SequenceGenerator(name = "orderitem_SEQ", sequenceName = "orderitem_SEQ", initialValue = 1,
allocationSize = 1)
public class OrderItemEntity extends BaseEntity {
//장바구니 번호
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "orderitem_SEQ")
private Integer orderItemId;
//외래키 정의
//장바구니상품 테이블은 장바구니 테이블과 연관되어 있다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id")
private OrderEntity orderEntity;
//외래키 정의
//장바구니상품 테이블은 상품 테이블과 연관되어 있다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_id")
private ProductEntity productEntity;
//장바구니 상품 수량
private Integer quantity;
//주문 상품 금액
private Integer orderPrice;
//주문 상태
private boolean orderStatus;
}
OrderItemRepository
package com.example.basic.Repository;
import com.example.basic.Entity.OrderItemEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface OrderItemRepository extends JpaRepository<OrderItemEntity, Integer> {
//주문 상품 찾기
@Query(value = "SELECT w FROM OrderItemEntity w WHERE w.orderEntity.orderId = :orderId")
List<OrderItemEntity> findAllByOrderId(@Param("orderId") Integer orderId);
@Query(value = "SELECT w FROM OrderItemEntity w WHERE w.orderEntity.orderId = :orderId AND w.productEntity.productId = :productId")
OrderItemEntity findByOrderIdAndProductId(Integer orderId, Integer productId);
//주문 상품 찾기
@Query(value = "SELECT w FROM OrderItemEntity w WHERE w.orderEntity.orderId = :orderId")
OrderItemEntity findByOrderId(Integer orderId);
@Query(value = "select w FROM OrderItemEntity w WHERE w.orderItemId = :orderItemId AND w.orderEntity.orderId = :orderId")
OrderItemEntity findByOrderItemIdAndOrderId(Integer orderItemId, Integer orderId);
@Query(value = "select w FROM OrderItemEntity w WHERE w.orderItemId = :orderItemId AND w.orderEntity.orderId = :orderId")
OrderItemEntity deleteById(Integer orderItemId, Integer orderId);
}
OrderService
package com.example.basic.Service;
import com.example.basic.DTO.OrderDTO;
import com.example.basic.DTO.OrderItemDTO;
import com.example.basic.Entity.*;
import com.example.basic.Repository.OrderItemRepository;
import com.example.basic.Repository.OrderRepository;
import com.example.basic.Repository.MemberRepository;
import com.example.basic.Repository.ProductRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Service
@RequiredArgsConstructor
@Transactional
public class OrderService {
private final OrderRepository orderRepository;
private final ModelMapper modelMapper;
private final MemberRepository memberRepository;
private final OrderItemRepository orderItemRepository;
private final ProductRepository productRepository;
//삽입
public void orderInsert(Integer memberId, Integer productId, Integer quantity
, Integer orderPrice) {
//회원 조회
MemberEntity memberEntity = memberRepository.findById(memberId)
.orElseThrow(() -> new IllegalArgumentException("Member not found"));
// 회원의 주문 테이블 조회
OrderEntity order = orderRepository.findByMemberId(memberEntity.getMemberId());
if (order == null) {
order = OrderEntity.builder()
.memberEntity(memberEntity)
.build();
orderRepository.save(order);
}
ProductEntity product = productRepository.findById(productId).orElseThrow();
// 주문 테이블에 상품이 이미 존재하는지 확인
OrderItemEntity orderItem = orderItemRepository.findByOrderIdAndProductId(order.getOrderId(), product.getProductId());
// 주문 테이블에 상품이 존재하지 않는다면 주문 테이블 생성 후 추가
if (orderItem == null) {
orderItem = OrderItemEntity.builder()
.orderEntity(order)
.productEntity(product)
.quantity(quantity)
.orderPrice(product.getProductPrice() * quantity)
.build();
orderItemRepository.save(orderItem);
} else {
// 주문 테이블에 상품이 존재하면 수량과 금액 변경
orderItem.setQuantity(orderItem.getQuantity() + quantity);
orderItem.setOrderPrice(orderItem.getOrderPrice() + orderPrice);
orderItemRepository.save(orderItem);
}
}
//주문 상태 확인
public void orderCheckout(Integer memberId, Integer orderItemId) {
//회원 조회
MemberEntity memberEntity = memberRepository.findById(memberId)
.orElseThrow(() -> new IllegalArgumentException("Member not found"));
// 회원의 주문 테이블 조회
OrderEntity order = orderRepository.findByMemberId(memberEntity.getMemberId());
if (order == null) {
order = OrderEntity.builder()
.memberEntity(memberEntity)
.build();
orderRepository.save(order);
}
//주문 테이블 상품 찾기
OrderItemEntity orderEntity = orderItemRepository.findById(orderItemId).orElseThrow();
//주문 테이블에 상품이 존재하면
if (orderEntity != null) {
//상품 정보 수정
orderEntity.setOrderStatus(true);
//주문상태 체크
}
orderItemRepository.save(orderEntity);
}
//삭제
public void orderDelete(Integer orderItemId, Integer orderId) {
orderItemRepository.deleteById(orderItemId, orderId);
}
//전체 조회(주문 상품)
public List<OrderItemDTO> orderList(Integer memberId) {
//회원의 주문 테이블 조회
OrderEntity order = orderRepository.findByMemberId(memberId);
//주문 테이블 상품 찾기
List<OrderItemEntity> orderEntities = orderItemRepository.findAllByOrderId(order.getOrderId());
//주문 테이블에 상품이 존재하면
if (orderEntities != null) {
//상품 정보 담기
List<OrderItemDTO> orderItemDTOS = new ArrayList<>();
for (OrderItemEntity orderItemEntity : orderEntities) {
//상품 리스트에 상품이 존재하면
if (orderItemEntity != null) {
OrderItemDTO orderItemDTO = modelMapper.map(orderItemEntity, OrderItemDTO.class);
orderItemDTOS.add(orderItemDTO);
}
}
return orderItemDTOS;
} else {
//주문 테이블에 상품이 존재하지 않으면
return Collections.emptyList();
}
}
//개별 주문
public OrderItemDTO itemDTO(Integer orderItemId, Integer orderId) {
//주문테이블 id와 주문상품테이블id로 조회
OrderItemEntity orderItemEntity = orderItemRepository.findByOrderItemIdAndOrderId(orderItemId, orderId);
OrderItemDTO orderItemDTO = modelMapper.map(orderItemEntity, OrderItemDTO.class);
return orderItemDTO;
}
//회원의 주문 테이블 개별조회
public OrderDTO orderDetail(Integer memberId) {
//회원의 장바구니 생성
OrderEntity order = orderRepository.findByMemberId(memberId);
OrderDTO orderDTO = modelMapper.map(order, OrderDTO.class);
return orderDTO;
}
}
@Transactional
모든 작업들이 성공적으로 완료되어야 작업 묶음의 결과를 적용하고, 어떤 작업에서 오류가 발생했을 때는 이전에 있던 모든 작업들이 성공적이었더라도 없었던 일처럼 완전히 되돌리는 것이 트랜잭션의 개념이다.
데이터베이스를 다룰 때 트랜잭션을 적용하면 데이터 추가, 수정, 삭제 등으로 이루어진 작업을 처리하던 중 오류가 발생했을 때 모든 작업들을 원상태로 되돌릴 수 있다. 모든 작업들이 성공해야만 최종적으로 데이터베이스에 반영하도록 한다.
@Transactional이 붙은 메서드는 메서드가 포함하고 있는 작업 중에 하나라도 실패할 경우 전체 작업을 취소한다.
OrderController
/*
설명 : 상품관리의 목록, 수정, 삭제, 조회로 이동하는 페이지 영역
입력값 : /order/list, /order/insert, /order/delete, /order/detail
출력값 : order/list, order/insert, order/detail
작성일 : 24.04.12
작성자 : 정아름
수정사항 :
*/
package com.example.basic.Controller;
import com.example.basic.DTO.*;
import com.example.basic.Service.MemberService;
import com.example.basic.Service.OrderService;
import com.example.basic.Service.ProductService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.util.List;
@Controller
@RequiredArgsConstructor
public class OrderController {
//주입
private final OrderService orderService;
private final MemberService memberService;
private final ProductService productService;
//전체 조회
@GetMapping("/order/list")
public String listForm(@AuthenticationPrincipal User user, Model model) throws NullPointerException {
//회원 정보가 있으면
//로그인 정보로 회원 조회
MemberDTO memberDTO = memberService.detail(user.getUsername());
if (memberDTO != null) {
//회원의 주문 목록 조회
List<OrderItemDTO> orderItemDTO = null;
if (orderItemDTO.get(1).isOrderStatus()) {
orderItemDTO = orderService.orderList(memberDTO.getMemberId());
}
model.addAttribute("totalCount", orderItemDTO);
model.addAttribute("mid", memberDTO.getMemberId());
model.addAttribute("list", orderItemDTO);
return "order/list";
//회원 정보가 없으면 상품 목록 페이지로 이동
} else {
return "redirect:/product/list";
}
}
//삽입(주문)
@PostMapping("/order/insert/{id}")
public String insertProc(@PathVariable Integer id,
@RequestParam Integer count,
@AuthenticationPrincipal User user,
RedirectAttributes redirectAttributes) {
//회원 정보가 없으면 상품 목록 페이지로 이동
if (user == null) {
return "redirect:/product/list";
}
//유저 정보 가져오기
String memberEmail = user.getUsername();
Integer memberId = memberService.detail(memberEmail).getMemberId();
ProductDTO productDTO = productService.productDetail(id);
//상품의 재고량이 0 보다 작다면 재고 부족 발생
if (productDTO == null || productDTO.getQuantityCount() < 0) {
redirectAttributes.addFlashAttribute("errorMessage",
"재고량이 부족합니다.");
return "redirect:/product/list";
}
Integer orderPrice = productDTO.getProductPrice() * productDTO.getQuantity();
orderService.orderInsert(memberId, productDTO.getProductId(), count,
orderPrice);
redirectAttributes.addFlashAttribute("successMessage",
"주문 페이지로 이동");
System.out.println(productDTO);
System.out.println(memberId);
System.out.println(count);
redirectAttributes.addAttribute("id", id);
return "redirect:/order/detail";
}
//주문 페이지
@GetMapping("/order/detail")
public String detailForm(@AuthenticationPrincipal User user, Model model,
Integer id) throws NullPointerException {
//회원 정보가 있으면
//로그인 정보로 회원 조회
MemberDTO memberDTO = memberService.detail(user.getUsername());
if (memberDTO != null) {
OrderDTO orderDTO = orderService.orderDetail(memberDTO.getMemberId());
//회원의 주문 목록 조회
List<OrderItemDTO> orderItemDTO = orderService.orderList(memberDTO.getMemberId());
OrderItemDTO order = null;
for (OrderItemDTO order1 : orderItemDTO) {
if (order1.getProductId().equals(id)) {
order = orderService.itemDTO(order1.getOrderItemId(), orderDTO.getOrderId());
}
}
model.addAttribute("order", orderDTO);
model.addAttribute("mid", memberDTO.getMemberId());
model.addAttribute("data", order);
return "order/detail";
//회원 정보가 없으면 상품 목록 페이지로 이동
} else {
return "redirect:/product/list";
}
}
//주문 페이지(주문 상태 체크)
@PostMapping("/order/detail")
public String detailProc(Integer mid, Integer id, Integer oid,
@AuthenticationPrincipal User user, Model model,
RedirectAttributes redirectAttributes) throws NullPointerException {
//로그인 정보로 회원 조회
MemberDTO memberDTO = memberService.detail(user.getUsername());
OrderDTO orderDTO = orderService.orderDetail(mid);
//회원 정보가 있으면
if (memberDTO.getMemberId().equals(mid) & orderDTO.getOrderId().equals(oid)) {
//회원의 주문 상태 변경
orderService.orderCheckout(memberDTO.getMemberId(), id);
model.addAttribute("id", id);
return "order/list";
//회원 정보가 없으면 상품 목록 페이지로 이동
} else {
return "redirect:/product/list";
}
}
//삭제
@GetMapping("/order/delete")
public String deleteProc(Integer mid, Integer id, RedirectAttributes redirectAttributes) {
OrderDTO orderDTO = orderService.orderDetail(mid);
orderService.orderDelete(id, orderDTO.getOrderId());
redirectAttributes.addFlashAttribute("successMessage",
"주문이 취소되었습니다.");
return "redirect:/order/list";
}
}
View
order/detail
<!--
파일명 : order/detail
작성자 : 정아름
작성일 : 24.04.12
수정사항 :
-->
<!DOCTYPE html>
<html lang="ko"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layouts/main}"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>SalPick</title>
<!-- bootstrap -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<!-- google fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nanum+Pen+Script&display=swap" rel="stylesheet">
<!-- google icon -->
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0"/>
<style>
body {
font-family: Verdana, sans-serif;
font-size: 15px;
}
h2 {
font-family: "Nanum Pen Script", cursive;
font-weight: 400;
font-size: 50px;
font-style: normal;
}
</style>
</head>
<body>
<div layout:fragment="content">
<div class="row">
<!-- 여백 -->
<div class="col-sm-3"></div>
<!-- 주문 상품 목록-->
<div class="col-sm-6">
<div class="container mt-5 mb-5">
<h2>주문 목록</h2>
<div class="row">
<div class="card">
<div class="card-body">
<table class="table table-hover">
<thead>
<tr>
<th>No</th>
<th>상품이미지</th>
<th>상품명</th>
<th>상품코드번호</th>
<th>상품수량</th>
<th>상품가격</th>
<th>총 금액</th>
</tr>
</thead>
<tbody>
<tr>
<td th:text="${data.orderItemId}">orderItemId</td>
<td>
<img class="img1" th:src="|/images/product/@{${data.productImage}}|"
alt="이미지 없음"
width="50%">
</td>
<td>
<a th:text="${data.productName}"
th:href="@{/product/detail(id=${data.productId})}">
productName
</a>
</td>
<td th:text="${data.productId}">productId</td>
<td th:text="${data.quantity}">quantity</td>
<td th:text="${data.productPrice}">productPrice</td>
<td th:text="${data.quantity}*(${data.productPrice})"></td>
</tr>
</tbody>
</table>
<form th:action="@{/order/detail(mid=${mid}, id=${data.orderItemId})}" method="post">
<input type="hidden" name="mid" th:value="${mid}">
<input type="hidden" name="id" th:value="${data.orderItemId}">
<input type="hidden" name="oid" th:value="${order.orderId}">
<button type="submit" class="btn btn-outline-danger float-end"
sec:authorize="hasAnyRole('USER', 'ADMIN')">
주문
</button>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- 여백 -->
<div class="col-sm-3"></div>
</div>
</div>
<script th:inline="javascript">
/* 작업 성공했을 때 성공메세지 창을 출력 */
var successMessage = /*[[ ${successMessage} ]]*/ null;
if (successMessage) {
alert(successMessage);
}
</script>
</body>
</html>
order/list
<!--
파일명 : order/list
작성자 : 정아름
작성일 : 24.04.12
수정사항 :
-->
<!DOCTYPE html>
<html lang="ko"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layouts/main}"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>SalPick</title>
<!-- bootstrap -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<!-- google fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nanum+Pen+Script&display=swap" rel="stylesheet">
<!-- google icon -->
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0"/>
<style>
body {
font-family: Verdana, sans-serif;
font-size: 15px;
}
h2 {
font-family: "Nanum Pen Script", cursive;
font-weight: 400;
font-size: 50px;
font-style: normal;
}
</style>
</head>
<body>
<div layout:fragment="content">
<div class="row">
<!-- 여백 -->
<div class="col-sm-3"></div>
<!-- 장바구니 상품 목록-->
<div class="col-sm-6">
<div class="container mt-5 mb-5">
<h2>주문 목록</h2>
<div class="row">
<div class="card">
<div class="card-body">
<table class="table table-hover">
<thead>
<tr>
<th>No</th>
<th>상품이미지</th>
<th>상품명</th>
<th>상품코드번호</th>
<th>상품수량</th>
<th>상품가격</th>
<th>총 금액</th>
</tr>
</thead>
<tbody>
<tr th:each="data:${list}">
<td th:text="${data.orderItemId}">orderItemId</td>
<td>
<img class="img1" th:src="|/images/product/@{${data.productImage}}|" alt="이미지 없음"
width="50%">
</td>
<td>
<a th:text="${data.productName}"
th:href="@{/product/detail(id=${data.productId})}">
productName
</a>
</td>
<td th:text="${data.productId}">productId</td>
<td th:text="${data.quantity}">quantity</td>
<td th:text="${data.productPrice}">productPrice</td>
<td th:text="${data.orderPrice}"></td>
</tr>
</tbody>
</table>
<button type="button" class="btn btn-outline-danger float-end"
th:onclick="|location.href='@{/order/delete(mid=${mid}, id=${id})}'|"
sec:authorize="hasAnyRole('USER', 'ADMIN')">
주문 취소
</button>
</div>
</div>
</div>
</div>
</div>
<!-- 여백 -->
<div class="col-sm-3"></div>
</div>
</div>
<script th:inline="javascript">
/* 작업 성공했을 때 성공메세지 창을 출력 */
var successMessage = /*[[ ${successMessage} ]]*/ null;
if (successMessage) {
alert(successMessage);
}
</script>
</body>
</html>
화면 캡쳐
'개발일지' 카테고리의 다른 글
Personal Project - 가계부 구현(1) (0) | 2024.05.08 |
---|---|
Personal Project - 가계부 구현 (2) | 2024.04.30 |
Team project - 쇼핑몰 구현(4) (0) | 2024.04.29 |
Team project - 쇼핑몰 구현(3) (0) | 2024.04.29 |
Team project - 쇼핑몰 구현(2) (0) | 2024.04.29 |