INTP의 멋대로 개발 세상

[📚상품 구매 사이트 3단계] 구매자 서버 만들기 - 5. 상품 목록보기/상세보기 만들기 본문

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

[📚상품 구매 사이트 3단계] 구매자 서버 만들기 - 5. 상품 목록보기/상세보기 만들기

인팁구름 2023. 4. 17. 17:21

로그인을 완료하면 상품 목록 페이지를 띄워준다.🛒

지금까지는 화면에 단순 글자만 넣어준 상태였는데,

실제 DB와 연결해 주려고 한다!

 


 

📺 화면 구현📺

 

상품 목록 화면

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

        <div class="container">
            <table class="table table-striped mt-4">
                <thead>
                    <tr>
                        <th>상품 번호</th>
                        <th>상품 이름</th>
                        <th>상품 가격</th>
                        <th>상품 재고</th>
                    </tr>
                </thead>
                <tbody>
                    <c:forEach items="${product}" var="p" varStatus="status">
                        <tr>
                            <td>${status.count}</td>
                            <td><a href="/product/${p.productId}">${p.productName}</a></td>
                            <td>${p.productPrice}원</td>
                            <td>${p.productQty}개</td>
                        </tr>
                    </c:forEach>
                </tbody>
            </table>
        </div>

        <%@ include file="../layout/footer.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;">
           
                <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>

            </div>
        </div>

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

 

 

 

ProductController

블로깅하면서 알았는데.. 따로 DTO를 만들지 않았다.

Model이랑 값이 똑같아서 바로 담은 거 같은데

Model이랑 같은 필드를 사용하더라도 꼭 DTO를 만드는 습관을 기르자! (보안상)

package shop.mtcoding.productapp_buyer.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
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 shop.mtcoding.productapp_buyer.model.product.Product;
import shop.mtcoding.productapp_buyer.model.product.ProductRepository;

@Controller
public class ProductController {

    @Autowired
    private ProductRepository productRepository;

	// 상품 목록 보기
    @GetMapping("/product")
    public String productList(Model model) {
        List<Product> productList = productRepository.findAll();
        model.addAttribute("product", productList);
        return "product/productList";
    }
    
	// 상품 상세 페이지
    @GetMapping("/product/{productId}")
    public String productDetail(@PathVariable Integer productId, Model model) {
        Product product = productRepository.findById(productId);
        model.addAttribute("product", product);

        return "product/productDetail";
    }

}

 

ProductRepository

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);

}

 

product.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.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>

 

 


🎤 코드 리뷰 🎤

 

1. 상품 목록 리스트 화면에 뿌리기

1.

DB에 있는 상품들을 페이지에 뿌리려면,

Repository의 findAll 메소드에 접근하면 된다!

 

 

findAll의 xml 쿼리문은 이렇게 작성되어있다.

product 테이블의 모든 컬럼들을 가져오는 것!

(id, name, price, qty...등등)

 

 

Repository에 findAll 메서드의 리턴 타입이 List이기 때문에

(어쩌고 보면 당연한 것. 모든 테이블 컬럼을 가져오는 거니까.)

 

2.

Repository에서 xml 파일에 접근한 뒤 데이터를 가지고 다시 컨트롤러에 돌아왔다.

컨트롤러에서도 List 로 가져왔고 그 List를 productList라는 변수에 담았다.

 

 

3.

그 productList 변수를 model에 담을 건데 "product" 라는 이름으로 담았다.

model 객체에 담는 메서드가 .addAttribute() 이다.

model에 담은 데이터는 JSP(화면)에 "product" 이름으로 들고갈 수 있다.

 

 

 

상품 목록 = 즉, 리스트이기 때문에

판매자가 상품을 등록하거나 삭제할 때마다 목록이 바뀔 것이다.

그렇기 때문에 forEach문을 사용해서 상품 리스트를 적어준다.

 

아까 컨트롤러에서 model 객체에 "product"라는 이름으로 데이터를 들고 왔다.

forEach문의 items 속성에 EL표현식을 써서 연결해 준다.

var는 내가 JSP에서 사용할 이름을 정한다라고 보면 되는데, 똑같이 product라고 써도 되지만

어디로 흘러가서 어디로 적용됐는 지 확인하기 위해 "p"라고 해 보았다.

 

그러면 이제 DB의 product 테이블의 컬럼들을 불러오고 싶을 때 "p"로 접근하면 되는 것이다.

화면에 데이터를 연결해주는 것이기 때문에 전부 EL 표현식을 써 주면 된다.

${p.productId}

${p.productName}

이런 식으로!

 

product 테이블은 현재 바나나, 딸기 2개의 더미 데이터가 있고

화면에도 위의 데이터와 같게 표시되는 것을 볼 수 있다.

(상품번호는 product_id가 아니다! 마지막에 설명할 것임!)

 

 

☝여기에 대한 설명!☝

 

user_id는 Primary key다.

처음 생성될 때 고유한 값을 가지므로, 삭제하면 영원히 다음 1번은 없다.

(id값이 1,2,3 순서로 생기는데 3번 id를 삭제하고 새로운 데이터를 생성해도 1,2,4,5...이런 식)

(이건 실제 DB를 쓰면 이렇다는 거고, H2-Console과 같은 가상 DB를 쓰면 서버를 새로 실행할 때마다 더미데이터 제외한 DB가 리셋되기 때문에 가상 DB에서는 해당하지 않는 이야기!!)

 

그래서

 

 

방금 설명한대로, 3번 데이터를 삭제한 뒤 새로운 데이터를 추가하면

product_id가 3번으로 시작하는 게 아니라 4번 부터 시작한다.

 

이건 primamry key를 화면에 띄운(?) 예시를 보여주기 위한 사진!! 위의 product 테이블과 상관 없음

 

그러면 화면에서 이런 식으로 표시될텐데 보는 입장에서는 ??? 왜 중간 숫자가 없지 뭐지? 싶을 것이다.

보통 Primary key인 id 값은 화면에 보여주는 용도가 아닌 다른 테이블에서 참고하는 용도(Foreign key)나 해당 테이블의 모든 정보들을(?) 가져올 때 사용하는 것 같다. product 테이블의 바나나, 딸기의 번호를 부여한다 정도.

 

어쨌든, 화면에서는 상품 순서대로 1,2,3.. 이렇게 순번(?)이 있어야 할 것이다.

순번은 말 그대로 순번이기 때문에 DB의 어떤 값과 전혀 상관이 없다.

그냥 순서를 세는 것!

 

 

우리는 상품 리스트를 JSP에서 forEach문으로 반복 출력하고 있다.

forEach문에 item, var 라는 속성이 있듯이 varStatus라는 속성이 있다.

varStatus = 상태용 변수
(그냥 지금 상태! 를 나타낼 변수를 하나 정하는 것이다. 위 코드를 보면 status라는 이름의 변수를 하나 만든 것)

 

varStatus 속성 중 count를 이용해 보자!😗

 

${status.current} 현재 for문의 해당하는 번호
${status.index} 0부터의 순서
${status.count} 1부터의 순서
${status.first} 첫 번째인지 여부
${status.last} 마지막인지 여부
${status.begin} for문의 시작 번호
${status.end} for문의 끝 번호
${status.step} for문의 증가값

 

위에서 아래로 깔끔하게 순번이 매겨진다!

 

 


 

2. 상품 상세보기

상품 목록에서 이름을 클릭하면 상세 페이지로 이동하게 만들었다.

 

 

상품 목록 페이지의 상품 이름 자리(=p.productName)에

a태그가 걸려있다.

주소는 컨트롤러와 동일하게 맞춰주어야 한다. (하지만 JSP에서 DB의 값들은 EL표현식을 써야 한다!!)

product의 id값은 상품마다 다르기 때문.

 

 

컨트롤러이다. 주소값에 productId가 들어간다.

왜냐면!

상품 리스트에 각각의 상품들은 고유의 primary key (=id)를 가지고 있다.

이렇게

바나나를 예시로 들자면,

바나나 글자를 클릭했을 때 바나나의 정보가 담긴 고유의 페이지로 이동을 하는 거니까

고유의 primary key인 id값이 필요한 것이다!!!! 두둥😤

productDetail의 매개변수에 @PathVariable로 productId를 넣어준다.

주소값에 변수가 들어갈 때 적어주는 어노테이션이다.

 

⏬GPT 설명이 최고다⏬

 

나머지는 상품 목록 볼 때와 같다.

목록을 불러올 때는 findAll 메소드를 사용했다면,

이번 상세 페이지에서는 상품 하나의 id를 찾아와야 되기 때문에 findById 메소드를 이용한다.

 

 

findById 메소드는 productId를 매개변수로 넣어 주어야 한다.

 

 

그렇게 하면 product에 있는 모든 컬럼들 중

내가 매개변수로 넣은 productId와 일치하는 값들만 들고와줄 것이다.

 

매개변수 자리에 6이 들어갔다면

딸기, 2000, 0, 2023-04-14 11:35:31 데이터만 들고온다는 뜻.

 

 

다시 컨트롤러로 돌아와 들고온 정보들을 product 객체에 담는다. (상품 이름, 가격, 재고..등)

상품의 정보를 "product"라는 이름으로 model에 담았다.

 

지저분한 CSS는 안 보인다 치고..

 

상품 상세 페이지에

모델에 담긴 "product" 이름으로 데이터 값을 넣은 모습이다.

상세 페이지이기 때문에 아까처럼 forEach 반복문을 쓸 필요가 없다. (하나의 상품만 보일 거니까)

 

 

 

각각 상품 상세 페이지로 들어갔을 때 나오는 화면!

Comments