INTP의 멋대로 개발 세상

[📚상품 구매 사이트 3단계] 구매자 서버 만들기 - 6-1. 상품 구매하기 - 주문 목록(주문 조회) 페이지 만들기 본문

KDT 풀스택 국비 과정/파이널 프로젝트(미니)

[📚상품 구매 사이트 3단계] 구매자 서버 만들기 - 6-1. 상품 구매하기 - 주문 목록(주문 조회) 페이지 만들기

인팁구름 2023. 4. 23. 02:31

 

구매자 입장에서 상품 상세페이지를 들어가면

아래에 구매 수량을 입력할 수 있는 input 태그와

input에 입력한 값 만큼 구매가 되는 버튼을 만들고,

구매가 완료되면 나의 주문목록 페이지로 이동하는 기능을 만들 것이다.

 

단순히 구매 수량만 submit 하는 게 아니라

재고 수량 내에서 구매할 수 있게 하고, 0개나 음수는 구매 못하게 자바스크립트로 막아주어야 하고,

구매를 했을 때 실제 상품의 재고(QTY) 값도 구매한 수량 만큼 차감되어야 한다.

이를 구현하려면 구매를 위한 insert와 함께

product 테이블의 정보를 update 해 주어야 한다.

다소 번거롭고 생각도 해야하는 부분이어서 쇼핑몰 프로젝트의 핵심 기능이 아닐까 싶다!💪💪

나도 이번에 구매라는 기능을 처음 만들어 봐서 구현하는 데에 시간이 많이 걸렸다.😂😂

 

(적다보니 게시글이 너무 길어져서 나눠서 작성하기로 했다)

 

 


 

일단 구매라는 기능을 구현하려면 테이블과 약간의 더미데이터가 필요하다!

첫 번째 게시글에 모든 테이블과 더미데이터가 있지만, 편의를 위해 다시 올리기🎇🎆

 

H2-Console 같은 가상 DB를 사용한다면 테이블명 뒤에 _tb 를 적어줘야 실행 시 서버가 터지지 않는다.

찾아봤는데.. 정확한 이유를 설명할 수가 없다😥 암튼 적어야 오류 안 남.

mariaDB나 mySQL 등을 사용하면 그대로 복붙하면 된다!

 

 

 

🧱 테이블 설계 및 더미 데이터 🧺

 

table.sql

create table orders(
    orders_id int primary KEY auto_increment,
    orders_name varchar(20) NOT null,
    orders_price int NOT null,
    orders_qty int NOT null,
    product_id int NOT null,
    user_id int NOT null,
    created_at TIMESTAMP
);

data.sql

INSERT INTO orders(orders_name, orders_price, orders_qty, product_id, user_id, created_at) VALUES ('바나나', 3000, 2, 1, 1, NOW());
INSERT INTO orders(orders_name, orders_price, orders_qty, product_id, user_id, created_at)  VALUES ('딸기', 2000, 5, 2, 2, NOW());

 

 


 

💃 모델링 🕺

 

 

처음에 orders 테이블에 대해서 의문을 가졌었다.

ordersName? Price? Qty? 이거 그냥 product 테이블에 있는 거랑 똑같은 거 아닌가? 라고.

물론 맞다. 내가 구매한 ordersName (바나나) = productName (바나나)이다.

하지만 상품 테이블은 엄연히 <상품> 테이블이고 나의 구매에 대한 정보가 담기지는 않는다.

<구매>에 대한 정보를 담은 테이블인 것이다.

 

 

Orders.java

 

 

ordersId = 말 그대로 "주문한 데이터의 id 값"이다. (PK)

ordersName = productName 내가 구매한 상품의 이름

ordersPrice = productPrice 내가 구매한 상품의 가격

ordersQty = productQty 내가 구매한 상품의 재고수량

productId = 어떤 상품을 구매했는 지 알기 위해 필요하다

userId = 누가 구매했는 지 알기 위해 필요하다

@Getter
@Setter
public class Orders {

    private Integer ordersId;
    private String ordersName;
    private Integer ordersPrice;
    private Integer ordersQty;
    private Integer productId;
    private Integer userId;
    private Timestamp createdAt;
}

OrdersDto.java

 

실제 구매 정보에는 구매상품 이름, 가격, 구매수량, 어느 상품인지 정도의 정보가 필요하다. (필요한 정보만을 DTO로 만듦)

id값은 보여지는 데이터 값이 아니라 구매 테이블의 정보에 대한 데이터를 불러올 때 사용하는 용도이다.

@Getter
@Setter
public class OrdersDto {

    private Integer ordersId;
    private String ordersName;
    private Integer ordersPrice;
    private Integer ordersQty;
    private Integer productId;

}

OrdersRepository.java

@Mapper
public interface OrdersRepository {

    // 2개 이상 @Param 붙이기
    public void insert(@Param("ordersDto") OrdersDto ordersDto, @Param("userId") Integer userId);

    public Orders findById(Integer ordersId);

    public List<Orders> findAll(Integer usersId);

    // 구매를 하면 productQty가 차감되어야 함
    public void orderUpdatebyProductQty(Orders orders);

    public int deleteById(Integer ordersId);
}

orders.xml

MyBatis는 항상 경로 잘 확인하고. Repository 메서드 이름과 id값이 일치하는 지 잘 확인하자!

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="shop.mtcoding.productapp_buyer.model.orders.OrdersRepository">

	<select id="findById"
		resultType="shop.mtcoding.productapp_buyer.model.orders.Orders">
		SELECT * FROM orders WHERE orders_id=#{ordersId}
	</select>

	<select id="findAll"
		resultType="shop.mtcoding.productapp_buyer.model.orders.Orders">
		SELECT *
		FROM orders 
		WHERE user_id=#{userId}
	</select>

	<insert id="insert">
		INSERT INTO orders(orders_name, orders_price, orders_qty, product_id, user_id, created_at)
		VALUES(#{ordersDto.ordersName}, #{ordersDto.ordersPrice}, #{ordersDto.ordersQty}, #{ordersDto.productId}, #{userId}, NOW())
	</insert>


	<delete id="deleteById">
		DELETE FROM orders WHERE orders_id = #{ordersId}
	</delete>

</mapper>

 

📺 화면 구현📺

 

 

구매하기 기능은 상품 상세보기 페이지에서 이어진다.

상품 상세보기 아래에 구매할 수량만큼 입력하고  구매하기 버튼을 누르면 구매가 되는 방식!

 

상품 상세 페이지 - 로그인 여부에 따라 하단바가 다르게 뜨도록 구현했다

 

productDetail.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
    <%@ include file="../layout/header.jsp" %>

        <div class="center">
            <div style="margin: 20px;">
                <form type="submit" action="/order/${productId}" method="post" onsubmit="return qtyCheck();">
                    <%-- productName 과 ordersName 연결하기 --%>
                        <input name="ordersName" type="hidden" value="${product.productName}">
                        <input name="ordersPrice" type="hidden" value="${product.productPrice}">
                        <table border="1" style="width: 500px; height: 200px; text-align: center;">

                            <tr style="border: 1px solid">
                                <th style="background-color: rgb(185, 185, 185)">상품명</th>
                                <th>${product.productName}</th>
                            </tr>
                            <tr style="border: 1px solid">
                                <th style="background-color: rgb(185, 185, 185)">상품가격</th>
                                <td>${product.productPrice}원</td>
                            </tr>
                            <tr style="border: 1px solid">
                                <th style="background-color: rgb(185, 185, 185)">상품재고</th>
                                <td>
                                    <span id="productQty">${product.productQty}</span>
                                    <span>개</span>
                                </td>
                            </tr>
                        </table>

                            <%-- 로그인 했을 때만 구매하기 버튼 뜨게 하기 --%>
                            <c:choose>
                                <c:when test="${principal != null}">
                                    <div class="center" style="margin-top: 20px; text-align: center;">

                                        수량 :<input name="ordersQty" type="number" min="0" class="form-control mb-3"
                                            style="width: 200px;">
                                        <button
                                            style="width: 240px; height: 50px; margin-right: 20px; background-color: rgb(255, 210, 199);">구매하기</button>
                                    </div>
                                </c:when>

                                <c:otherwise>
                                    <div class="center" style="margin-top: 40px; text-align: center;">
                                        <h5>상품을 구매하시려면 로그인 해주세요😀</h5>
                                    </div>
                                </c:otherwise>
                            </c:choose>
                </form>


            </div>
        </div>

        <script>
            function qtyCheck() {
                let ordersQty = parseInt(document.getElementsByName("ordersQty")[0].value); // 주문수량 150
                let productQty = parseInt(document.getElementById("productQty")); //재고수량 95

                // 반복 코드 줄이기 위해 return false; 를 변수화
                let ret = false;

                console.log("orderQty : " + ordersQty);
                console.log("productQty : " + productQty);

                // 주문 수량이 undefined인지 여부
                if (ordersQty != null && productQty != null) {
                    // 주문수량이 존재함
                    // 주문수량 > 재고
                    if (ordersQty > productQty) {
                        alert("재고 수량을 초과하여 구매할 수 없습니다.");
                    }
                    // 주문수량 = 0 or 주문수량 < 0
                    else if (ordersQty === 0 || ordersQty < 0) {
                        alert("1개 이상 구매할 수 있습니다.");
                    }
                    // 그게 아니라면 true로 반환 !ret 이니까 true임
                    else {
                        ret = !ret;
                    }
                } else {
                    // 주문수량이 undefined임
                    alert("주문수량을 입력해 주세요.");
                }
                return ret;
            }
        </script>

        <%@ include file="../layout/footer.jsp" %>

 

 

ordersList.jsp

 

 

상품을 구매하면 구매목록 페이지로 이동한다.

 

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
    <%@ include file="../layout/header.jsp" %>
        <div class="text-center m-4">
            <h1>구매목록 페이지</h1>
        </div>
        <div class="container">
            <table class="table table-striped">
                <thead>
                    <tr>
                        <th>주문번호</th>
                        <th>상품명</th>
                        <th>상품가격</th>
                        <th>구매수량</th>
                        <th>가격</th>
                        <th>비고</th>
                    </tr>
                </thead>
                <tbody>
                    <c:forEach items="${orderedProduct}" var="op" varStatus="status">
                        <tr>
                            <td>${status.count}</td>
                            <td>${op.ordersName}</a></td>
                            <td>${op.ordersPrice}원</td>
                            <td>${op.ordersQty}개</td>
                            <td>${op.ordersPrice * op.ordersQty}원</td>
                            <td>
                                <form action="/orderListForm/delete" method="post">
                                    <input name="ordersId" type="hidden" value="${op.ordersId}">
                                    <button class="btn btn-success btn-sm" type="submit">취소하기</button>
                                </form>
                            </td>

                        </tr>
                    </c:forEach>
                </tbody>
            </table>
        </div>

        <%@ include file="../layout/footer.jsp" %>

 

OrdersController.java

package shop.mtcoding.productapp_buyer.controller;

import java.util.List;

import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
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 shop.mtcoding.productapp_buyer.dto.orders.OrdersDto;
import shop.mtcoding.productapp_buyer.handler.ex.CustomException;
import shop.mtcoding.productapp_buyer.model.orders.Orders;
import shop.mtcoding.productapp_buyer.model.orders.OrdersRepository;
import shop.mtcoding.productapp_buyer.model.product.Product;
import shop.mtcoding.productapp_buyer.model.product.ProductRepository;
import shop.mtcoding.productapp_buyer.model.user.User;
import shop.mtcoding.productapp_buyer.model.user.UserRepository;

@Controller
public class OrderController {

    @Autowired
    private OrdersRepository ordersRepository;

    @Autowired
    private ProductRepository productRepository;

    @Autowired
    private HttpSession session;

    // 구매 목록 페이지
    @GetMapping("/orderListForm/{userId}")
    public String orderListForm(@PathVariable Integer userId, Model model) {

        User principal = (User) session.getAttribute("principal");

        // 로그인 안한 사람이 주문목록 보려고 시도할 시
        if (principal == null) {
            throw new CustomException("구매목록을 볼 권한이 없습니다.", HttpStatus.FORBIDDEN);
        }

        // 로그인 했지만 나 아닌 다른 사람의 주문목록 보려고 시도할 시
        // ! <- 논리 부정 연산자
        if (!principal.getUserId().equals(userId)) {
            throw new CustomException("구매목록을 볼 권한이 없습니다.", HttpStatus.FORBIDDEN);
        }

        List<Orders> ordersList = ordersRepository.findAll(userId);
        model.addAttribute("orderedProduct", ordersList);

        return "order/orderListForm";
    }

    // 상품 구매하기
    // 어떤 상품을 구매했는 지 알아야해서 주소에 productId가 필요함(?) <--확인하기
    @PostMapping("/order/{productId}")
    public String order(@PathVariable Integer productId, OrdersDto ordersDto) {

        // 로그인 한 사람만 구매할 수 있음
        User principal = (User) session.getAttribute("principal");
        if (principal == null) {
            throw new CustomException("로그인을 먼저 해 주세요.", HttpStatus.FORBIDDEN);
        }

        // 상품수량보다 구매수량이 더 많으면 안됨
        Product productPS = productRepository.findById(productId);
        if (productPS.getProductQty() - ordersDto.getOrdersQty() < 0) {
            throw new CustomException("재고보다 더 많은 수량을 구매할 수 없습니다.", HttpStatus.FORBIDDEN);
        }

        // 구매를 하면 product qty가 차감되어야 함
        productRepository.productQtyUpdate(ordersDto);

        // principal.getUserId() 너무 길어서 변수로 만듦
        int userId = principal.getUserId();

        /*
         * 구매버튼 누르면 insert 됨
         * 누가 구매했는 지 필요하기 때문에 userId도 같이 insert 해야 함
         */
        ordersRepository.insert(ordersDto, userId);
        return "redirect:/orderListForm/" + userId;

    }

    @PostMapping("/orderListForm/delete")
    public String deleteOrder(Integer ordersId, Integer productId) {

        // 로그인 한 사람만
        User principal = (User) session.getAttribute("principal");
        if (principal == null) {
            throw new CustomException("로그인을 먼저 해 주세요.", HttpStatus.FORBIDDEN);
        }

        int userId = principal.getUserId();

        System.out.println("userId : " + userId);

        // productRepository.findById(productId);
        // System.out.println("productId : " + productId);

        // 구매 취소했으니 다시 product Qty 업데이트
        // productRepository.productQtyReupdate(ordersDto);
        // System.out.println("재고 : " + ordersDto.getOrdersQty());

        Orders orders = ordersRepository.findById(ordersId);
        productRepository.productQtyReupdate(orders);

        // 주문 정보 삭제
        ordersRepository.deleteById(ordersId);

        return "redirect:/orderListForm/" + userId;
    }
}

 

OrdersRepository.java

package shop.mtcoding.productapp_buyer.model.orders;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import shop.mtcoding.productapp_buyer.dto.orders.OrdersDto;

@Mapper
public interface OrdersRepository {

    // 2개 이상 @Param 붙이기
    public void insert(@Param("ordersDto") OrdersDto ordersDto, @Param("userId") Integer userId);

    public Orders findById(Integer ordersId);

    public List<Orders> findAll(Integer usersId);

    // 구매를 하면 productQty가 차감되어야 함
    public void orderUpdatebyProductQty(Orders orders);

    public int deleteById(Integer ordersId);
}

 

ProductRepository.java

 

상품을 구매하거나 취소할 때, 기존 상품 테이블에도 수량의 변화가 있어야 하기 때문에

구매되거나 취소된 정보를 업데이트 해 주어야 한다.

 

package shop.mtcoding.productapp_buyer.model.product;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import shop.mtcoding.productapp_buyer.dto.orders.OrdersDto;
import shop.mtcoding.productapp_buyer.model.orders.Orders;

@Mapper
public interface ProductRepository {

    public Product findById(Integer productId);

    public List<Product> findAll();

    public Product findByProductName(String productName);

    public void insert(Product product);

    public void update(Product product);

    public void deleteById(Integer productId);

    // 구매 시에 product QTY가 차감 되어야 함
    public void productQtyUpdate(OrdersDto ordersDto);

    // 구매 취소시 prouduct QTY 다시 증가
    public void productQtyReupdate(Orders orders);

}

 

 

 

orders.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="shop.mtcoding.productapp_buyer.model.orders.OrdersRepository">

	<select id="findById"
		resultType="shop.mtcoding.productapp_buyer.model.orders.Orders">
		SELECT * FROM orders WHERE orders_id=#{ordersId}
	</select>

	<select id="findAll"
		resultType="shop.mtcoding.productapp_buyer.model.orders.Orders">
		SELECT *
		FROM orders 
		WHERE user_id=#{userId}
	</select>

	<insert id="insert">
		INSERT INTO orders(orders_name, orders_price, orders_qty, product_id, user_id, created_at)
		VALUES(#{ordersDto.ordersName}, #{ordersDto.ordersPrice}, #{ordersDto.ordersQty}, #{ordersDto.productId}, #{userId}, NOW())
	</insert>


	<delete id="deleteById">
		DELETE FROM orders WHERE orders_id = #{ordersId}
	</delete>

</mapper>

 

product.xml

 

productQtyUpdate = 구매 시 업데이트 할 정보 ( 상품재고에서 - 구매수량 )

productQtyReupdate = 구매 취소 시 업데이트 할 정보 ( 상품재고에서 + 구매수량 )

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="shop.mtcoding.productapp_buyer.model.product.ProductRepository">

	<select id="findById"
		resultType="shop.mtcoding.productapp_buyer.model.product.Product">
		SELECT * FROM product WHERE product_id = #{productId}
	</select>

	<select id="findByProductName"
		resultType="shop.mtcoding.productapp_buyer.model.product.Product">
		SELECT * FROM product WHERE product_name = #{productName}
	</select>

	<select id="findAll"
		resultType="shop.mtcoding.productapp_buyer.model.product.Product">
		SELECT * FROM product
	</select>


    <update id="productQtyUpdate">
        Update product
		SET product_qty = product_qty - #{ordersQty}
		where product_id = #{productId}
    </update>

	<update id="productQtyReupdate">
        Update product
		SET product_qty = product_qty + #{ordersQty}
		where product_id = #{productId}
    </update>

	<insert id="insert">
		INSERT INTO product(product_name, product_price, product_qty, created_at)
		VALUES(#{productName}, #{productPrice}, #{productQty}, NOW())
	</insert>

	<update id="update">
		UPDATE product SET 
		product_name = #{productName},
		product_price = #{productPrice},
		product_qty = #{productQty}
		WHERE product_id = #{productId}
	</update>

	<delete id="deleteById">
		DELETE FROM product WHERE product_id = #{productId}
	</delete>


    

</mapper>

 


💫 예외 처리 💫

 

접근을 막기 위해, 예외처리 클래스를 만들어 보자

handler 폴더에 Handler 클래스와

handler\ex 폴더 안에 CustomException 클래스를 만든다.

 

CustomExceptionHandler.java

package shop.mtcoding.productapp_buyer.handler;

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import shop.mtcoding.productapp_buyer.handler.ex.CustomException;

@RestControllerAdvice
public class CustomExceptionHandler {

    // 무조건 데이터를 응답할 거기 때문에 RestController
    // 자바 스크립트를 응답
    // 어노테이션 ()안에 타입을 적어줘야 함
    // 런타임익셉션 - 모든 익셉션이 다 여기로 올 거임

    // exception터지면 다 여기로 올 거임! 무조건 뒤로가기

    @ExceptionHandler(CustomException.class)
    public String basicException(Exception e) {
        StringBuilder sb = new StringBuilder();
        sb.append("<script>");
        sb.append("alert('" + e.getMessage() + "');");
        sb.append("history.back();");
        sb.append("</script>");
        return sb.toString();
    }

    // Dto를 응답

}

 

CustomException.java

package shop.mtcoding.productapp_buyer.handler.ex;

import org.springframework.http.HttpStatus;

public class CustomException extends RuntimeException {

    // 상태코드
    private HttpStatus status;

    // 생성자
    public CustomException(String message, HttpStatus status) {
        super(message);
        this.status = status;
    }
}

 

 


🎤 코드 리뷰 - 구매 목록 페이지 🎤

 

 

우선 상품 상세 페이지(productDetail) 에서 구매하기 버튼을 만들 것이다.

위에서도 설명했지만, 로그인 여부에 따라 하단부에 뜨는 내용이 다르다.

이전 게시글에 로그인 한 정보를 principal에 담았었다.

 

 

확인해 보기 ⏬

로그인 컨트롤러
상품 상세페이지 컨트롤러 (principal에 대한 매개변수나 정보가 없음에도 productDetail.jsp 화면에서 principal을 쓸 수 있다

 

 

로그인 한 정보는 세션이 유지되는 동안 principal에 정보가 담겨있다.

그래서 상품 상세페이지를 불러오는 컨트롤러에서

굳이 principal을 매개변수로 받지 않아도 productDetail 페이지를 리턴할 때,

principal의 정보를 가져갈 수 있다.

 

 

productDetail.jsp

 

이렇게, principal이 != null

null이 아닐 때라는 뜻이니까 principal이 있다는 뜻

로그인 했을 때는  아래의 수량을 입력하는 input 태그와 구매하기 버튼을 띄우고

그게 아닐 경우(otherwise) 로그인을 먼저 해달라는 문구를 띄운다.

 

 

이제 구매하기 기능을 만들기 위해 DTO를 만들자!

 

 

ordersId = 주문한 건의 고유 id 값,

ordersName = 주문한 상품의 이름

ordersPrice = 주문한 상품의 가격

ordersQty = 구매한 수량

productId = 구매한 상품의 id 값 (어느 상품을 구매했는지 정보가 필요해서)

 

 

 

상품의 상세페이지에서

구매자는 원하는 상품의 상세페이지에서 구매하고 싶은 수량을 입력할 것이다.

 

그러면 구매 정보를 담은 orders 테이블에

구매한 상품의 이름, 가격, 구매수량에 대한 데이터가 필요할 것이다.

그런데 구매한 상품의 이름이랑 가격은 product의 name과 price랑 같다!

화면에 있는 (상세 페이지에 있는) product의 데이터와 orders 데이터를 연결해 주자📦📦

 

 

 

이럴 때는 input 태그의 type을 hidden으로 하면

실제 화면에 보이지는 않지만 데이터를 전달해 줄 수 있다.

name 값은 DTO의 명과 같게 해주어야 하고, value에 전달할 데이터를 입력하면 된다.

 

hidden 태그를 사용하는 것은 웹 페이지와 서버 간에 데이터를 전달하는 일반적인 방법이다.

잘 보면 form 태그 안에 hidden 태그가 있다.

그렇기 때문에 form 태그가 제출(submit)될 때

(= 즉, 여기서는 구매하기 기능이 실행될 때)

양식 제출의 일부로 데이터를 전송할 수 있으며, 서버 측 코드로 처리되어 응답을 생성하는 데 사용할 수 있다.

(물론 다른 방법도 있긴 하다.)

 

 

 

 

아래에 수량을 입력한 input 태그 역시 구매 정보에 필요하므로 name 값에 DTO 이름과 똑같이 적어준다

(id 아니고 꼭 name을 써야 한다!!!)

⏬ 당연하다 생각했지만 블로그 쓰다가 나도 왜인지 궁금해져서 GPT한테 물어봤다 ㅎ⏬

요약하자면 그냥 당연히 name 값이 DTO랑 연결되는 거라서..그렇게 정해진 거라서..

 

 

일단 구매목록을 보는 페이지를 만들었으니

컨트롤러와 먼저 연결시켜주자

 

 

구매 목록 페이지도 상품목록 페이지와 데이터 값만 다르지, 코드 내용은 같은데,

다른 점은 구매목록은 로그인한 자신의 것만 봐야한다는 것이다.

상품목록이나 상품 상세 페이지는 로그인을 하지 않아도 볼 수 있기 때문에

따로 접근하는 데에 제한을 주는 코드는 없었다.

하지만 구매 목록 페이지는 필요하다.

 

위에 빨간 박스가 로그인이 필요한 부분,

초록색 박스는 구매 정보를 리스트 형태로 뿌려서 모델 객체에 담아주는 부분이다.

 

상품 목록 페이지와 비교

 

 

주문 목록 페이지로 가는 주소값에 유저의 고유한 아이디 값을 넣었다.

주소값에 변하는 데이터의 값을 넣으면 @PathVariable 어노테이션을 매개변수 자리에 넣어주어야 한다.

주소값에 {userId} 같은 값이 없다면 쓸 필요 없다.

 

 

(근데 나 이거 최근에 알았다😂 이 때까지 생각없이 씀..충격

 

아 그리고 만들 때는 몰랐지만,

아래부터 session 값을 가져오는 코드를 쓸 텐데

session으로 로그인 한 유저를 걸러내면 굳이 주소값에 {userId}를 쓰지 않아도

알아서 로그인 한 사람의 구매 목록 페이지로 들어간다. (...)

그럼 사실 @PathVariable도 쓸 필요 없다 (....)

 

 

그리고 session에서 "principal"이라는 이름으로 저장된 속성을 가져와서

User principal에다가 넣는다.

(Java 언어의 기본은 우항의 값을 좌항에 대입한다)

 

session이라는 이름은 HttpSession이라는 객체를 우리가 session이라고 정했고,

로그인 할 때 session에다가 로그인 정보를 "principal"이라는 이름으로 저장해 놨다.

이름을 같게 만들어서 헷갈릴 수도 있지만.. 좌항의 principal은 새로 만든 객체의 이름일 뿐이고, 우항의 "principal" 이 진짜 로그인 정보가 담겨있는 값이다.

 

그래서 session에 저장된 값을 get 해서 가져온 것이다.

어 근데 session 앞에 (User)는 뭐냐고?

= getAttribute 를 "다운캐스팅" 한 것이다!

 

getAttribute와 setAttribute는 리턴 타입이 Object이다.

(이유는 없고 만든 사람이 그렇게 만들었다.)

그렇기 때문에 위 처럼 getAttribute을 써서 대입하려면 리턴값(왼쪽)의 타입을

사진처럼 Object로 써야하는데 Object라고 쓰면 컴파일 에러가 날 것이다.

왜냐하면 세션에 저장된 "principal"의 정보는 User 클래스에 있기 때문이다.

Object는 모든 클래스의 상위개념이다.

즉 모든 클래스는 Object를 상속하고 있다.

 

 

그림으로 나타내면 이렇다. User 클래스가 하위 개념이다.

하위 클래스가 들고 있는 정보이니 당연히 상위 클래스인 Object가 들고있을 리 없다.

 

그러니 이렇게 같은 User로 맞춰주게끔 다운캐스팅을 하는 것이다!!

 

 

 

어쨌든, 그렇게 새로운 principal 객체에 담았다 (이름은 달라도 상관없다)

 

먼저, 로그인을 아예 안한 사람이 주소(Url)로 접근하는 것을 막아야 한다.

로그인 안한 사람이 주소창에

localhost:8080/orderListForm/1

이런 식으로 칠 수도 있는데 들어와지면 안되니까!🤨

principal == null (아예 로그인 정보가 없다는 뜻) 이면

CustomException으로 던져 버린다

 

두 번째로 로그인은 했지만 내 주문 목록이 아닌 다른 사람의 주문 목록을 보려고 시도할 때이다.

코드는 영어 그대로 해석이 된다.

principal (로그인 한 사람의)

getUserId (UserId 데이터를갖고왔는데)

equals (userId)

주소값의 userId랑 같다.

 

그런데 맨 앞에 " ! " 를 붙임으로써

"로그인 한 사람의 Id가 주소값의 userId와 다를 때" 라는 뜻이 된다.

역시나 그런 경우가 발생하면 CustomException으로 예외처리를 한다.

 

두 번째 조건은 같지 않음을 나타내는데, 첫 번째 조건과 다르게

!= 같은 연산자를 안 사용하고 왜 .equals() 메서드를 사용할까?

 

 

 

ProductList를 만들 때와 같이 로그인 한 userId 를 매개변수로 넣어

구매목록을 findAll 메서드로 가져와서 orderList 라는 객체에 담아주었고,

orderList 를

model 객체에 "orderedProduct" 라는 이름으로 담아주었다.

 

 

jsp에서 forEach문을 사용해 model에 담은 orderedProduct를 가져오고 이름이 너무 기니까 op라고 정해주었다.

총 구매 가격은 그냥 심심해서 만들어 보았다 😋 (저렇게도 되나 궁금해서 ㅋㅋㅋ)

(취소하기 버튼은 아직 신경쓰지 말자 지워도 된다)

 

 

 

헤더 주소를 수정해 주자!

@GetMapping의 주소와 같게 적어주면 되는데,

여기는 jsp 화면이기 때문에 principal의 정보를 들고와 주어야 한다.

저 주소의 principal.userId가!

 

 

바로바로 여기로 들어와 주는 것이다!! 🙄

그래서 로그인 한 유저의 정보만 보여줄 수 있는 것이다!!

헉 대박

 


 

📊 결과 확인 📊

 

 


로그인 유무에 따라 상품 상세페이지의 하단 부분이 바뀌어 있다.

 

 

1번 유저(ssar)로 로그인 해서 헤더의 주문확인 탭을 누르면 구매목록 페이지가 성공적으로 뜰 것이다✨🎉

 

 

 

 

 

 

 

 

 

 

 

Comments