WEB/React

홈페이지 만들어보기[상품 목록 조회](with SpringBoot) - (7)

luckydadit 2025. 2. 20. 17:08

1. 상품 목록 조회 이벤트 처리하기

1.1 상품 정보 테이블 만들기

 Category.java에 추가 (with. IntelliJ)
package com.coffee.constant;

// 상품의 카테고리 정보를 나타내기 위한 열거형 상수
public enum Category {
    BREAD("빵"),
    BEVERAGE("음료수"),
    CAKE("케익");

    private final String description;

    Category(String description) {
        this.description = description;
    }
    
    public String getDescription() {
        return description;
    }
}

Product.java에 추가 (with. IntelliJ)
package com.coffee.entity;

import com.coffee.constant.Category;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

import java.time.LocalDate;

// 상품 1개를 의미하는 자바 클래스

@Getter
@Setter
@Entity
@Table(name = "products")
public class Product {
    // 엔터티 코딩 작성시 실제 제약 조건도 고려해야 합니다.
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private int price = 0;

    @Enumerated(EnumType.STRING)
    private Category category; // Enum으로 변경 예정

    @Column(nullable = false)
    private int stock = 0;

    @Column(nullable = false)
    private String image;

    @Column(nullable = false, length = 1000)
    private String description;

    private LocalDate inputdate;

}
위의 Products.java 파일을 생성 후, coffeeApplication을 Run하면 Console 창에 아래와 같이 테이블이 생성됩니다.
Hibernate: 
    create table products (
        id bigint not null auto_increment,
        category enum ('BEVERAGE','BREAD','CAKE'),
        description varchar(1000) not null,
        image varchar(255) not null,
        inputdate date,
        name varchar(255) not null,
        price integer not null,
        stock integer not null,
        primary key (id)
    ) engine=InnoDB

 

<MySQL 실행결과> 

 

1.2 상품 정보 조회 처리 로직 생성하기(with. SpringBoot)

 

ProductController.java에 추가 (with. IntelliJ)
package com.coffee.controller;

import com.coffee.entity.Product;
import com.coffee.service.ProductService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

// 상품 정보 요청을 처리해주는 Controller
// RequestMapping에 여러 개의 요청을 처리하려면 중괄호 기호를 사용하면 됩니다.
@RestController
@RequestMapping({"product", "products"})
public class ProductController {
    private ProductService productService;

    public ProductController(ProductService productService) {
        this.productService = productService; // 생성자 주입
    }

    @GetMapping("/list") // 상품 목록 보여 주세요.
    public ResponseEntity<List<Product>> list(){
        List<Product> products = this.productService.listProducts();
        System.out.println("상품 개수 : " + products.size());

        // http 응답 코드 200(성공)과 함께 상품 정보를 json으로 반환해줍니다.
        return ResponseEntity.ok(products);
    }


}

 

ProductService.java에 추가 (with. IntelliJ)
package com.coffee.service;

import com.coffee.entity.Product;
import com.coffee.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ProductService {
    @Autowired
    private ProductRepository productRepository;

    public List<Product> listProducts(){
        return this.productRepository.findAllByOrderByIdDesc();
    }

}

 

ProductRepository.java에 추가 (with. IntelliJ)
package com.coffee.repository;

import com.coffee.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface ProductRepository extends JpaRepository<Product, Long> {
    List<Product> findAllByOrderByIdDesc(); // id를 기준으로 내림차순 정렬하기

}

 

<실행결과>

URL - http://localhost:9000/product/list

SpringBoot의 콘솔창에 확인한 결과(with. IntelliJ)
Hibernate: 
    select
        p1_0.id,
        p1_0.category,
        p1_0.description,
        p1_0.image,
        p1_0.inputdate,
        p1_0.name,
        p1_0.price,
        p1_0.stock 
    from
        products p1_0 
    order by
        p1_0.id desc
상품 개수 : 0

 

1.3 상품 정보 조회 화면 만들기(with. React)

ProductList.js에 추가(with. VSCode)
function App(){
    return(
        <>
         상품 목록 페이지
        </>

    );
};

export default App;

 

App.js에 추가 및 수정한 부분(with. VSCode)
// 라우팅 관련 컴포넌트
import HomePage from './pages/HomePage';
import LoginPage from './pages/LoginPage';
import SignupPage from './pages/SignupPage';
import ProductList from './pages/ProductList';
                <Route path='/member/login'  element={<LoginPage setUser={handleLoginSuccess} />}/>
                <Route path='/member/signup' element={<SignupPage/>}/>
                <Route path='/member/list' element={<ProductList/>}/>
            default:
                return(
                <>
                    <Nav.Link style={{color:"white"}} href='/member/list'>상품 보기</Nav.Link>
                    <Nav.Link style={{color:"white"}} href='/member/login'>로그인</Nav.Link>
                    <Nav.Link style={{color:"white"}} href='/member/signup'>회원 가입</Nav.Link>
                </>

 

<적용결과>

 

상품 보기 클릭시, ProductList.js 페이지로 이동을 확인할 수 있습니다.

 

1.4 상품 정보 임시 데이터 만들기(with. IntelliJ)

 

1.4.1 테스트 환경 구성하여 사전 테스트해보기

pom.xml 내용 수정

 

<scope>test</scope> 를 제거한 후, ProductTest.java 파일을 생성합니다. (@SpringBootTest와 관련됨)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>

 

 

ProductTest.java 추가

package com.coffee.test;

import com.coffee.common.GenerateData;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class ProductTest {

    @Test
    public void testCreateProducts(){
        List<String> imageFileList = GenerateData.getImageFileNames();

        for(String image : imageFileList){
            System.out.println(image);
        }
    }
}

 

 

GenerateData.java 추가

package com.coffee.common;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class GenerateData {
    // 윈도우 폴더 구분자는 \인데 특수 문자이므로 반드시 \\로 표기해야 합니다.
    private static final String IMAGE_DIR = "d:\\boot\\images";

    public static List<String> getImageFileNames() {
        // 특정 폴더 내에 들어 있는 모든 이미지 파일을 문자열 List 형식으로 반환합니다.
        File folder = new File(IMAGE_DIR);
        List<String> imageFiles = new ArrayList<String>();

        if(folder.exists() == false && folder.isFile()){
            System.out.println("해당 폴더가 존재하지 않습니다." + IMAGE_DIR);

            return imageFiles;
        }

        // 확장자가 다음 항목인 파일들만 추출
        String[] imageExtensions = {".jpg", ".jpeg", ".png"};

        File[] fileList = folder.listFiles();

        for(File file : fileList){
            if (file.isFile() && Arrays.stream(imageExtensions)
                    .anyMatch(ext -> file.getName().toLowerCase().endsWith(ext))) {
                imageFiles.add(file.getName());
            }
        }

        return  imageFiles;
    }
}

 

<테스트 결과>

ProductTest 를 실행(Run)한 후, 콘솔창을 확인합니다.

americano01.png
americano02.png
americano03.png
avante.png
brioche_01.png
brioche_02.png
brioche_03.png
brioche_04.png
brioche_05.png
cappuccino01.png
cappuccino02.png
cappuccino03.png
chicago.jpg
child1.jpg
... (이하 생략)

 

1.4.2 테스트 환경을 사용하여 MySQL(DB)에 샘플 데이터 insert하기

ProductTest.java 수정 부분 (with. IntelliJ)
package com.coffee.test;

import com.coffee.common.GenerateData;
import com.coffee.constant.Category;
import com.coffee.entity.Product;
import com.coffee.repository.ProductRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.time.LocalDate;
import java.util.List;

@SpringBootTest
public class ProductTest {

    @Autowired
    private ProductRepository productRepository;
    
    @Test
    public void testCreateProducts(){
        List<String> imageFileList = GenerateData.getImageFileNames();

//        for(String image : imageFileList){
//            System.out.println(image);
//        }
        for (int i = 0; i < imageFileList.size(); i++) {
            Product bean = new Product();
            //bean.setId();
            bean.setName("상품" + i);
            bean.setCategory(Category.BREAD);
            bean.setImage(imageFileList.get(i));
            bean.setPrice(10*(i+1));
            bean.setStock(100*(i+1));
            bean.setDescription("상품 설명 " + i);
            bean.setInputdate(LocalDate.now());

            productRepository.save(bean);
        }
    }
}

 

<실행 결과>

ProductTest 를 실행(Run)한 후, 콘솔창을 확인합니다.
Hibernate: 
    insert 
    into
        products
        (category, description, image, inputdate, name, price, stock) 
    values
        (?, ?, ?, ?, ?, ?, ?)
Hibernate: 
    insert 
    into
        products
        (category, description, image, inputdate, name, price, stock) 
    values
        (?, ?, ?, ?, ?, ?, ?)
----------(이하 생략)-------------

 

<MySQL 결과 확인>

 

1.4.3 샘플 데이터를 기반으로 상품 목록 보기에 보여주기(with. React)

ProductList.js에서 수정한 부분(with. VS Code)
import axios from "axios";
import { useEffect, useState } from "react";
import { Container, Row } from "react-bootstrap";

function App(){
    const [products, setProducts] = useState([]); // 상품 목록 state

    useEffect(() => { // BackEnd 서버에세 데이터를 읽어 오기
        axios.get("http://localhost:9000/product/list")
            .then((response) => {
                console.log('response.data');
                console.log(response.data);

                setProducts(response.data || []); // 데이터가 안올 경우, []로 대체한다는 의미임.
            })
            .catch((error) => {
                console.error(error);
            });
    }, []); // 2번째 매개 변수 []로 인하여 딱 1번만 rendering합니다.

    return(
        <Container className="my-4">
            <h1>상품 목록 페이지</h1>
            <Row>

            </Row>
        </Container>

    );
};

export default App;

 

<적용결과>

 

ProductList.js에서 수정한 부분(with. VS Code)
        <Container className="my-4">
            <h1>상품 목록 페이지</h1>
            <Row>
                {products.map((product) => (
                    <Col key={product.id} md={4} className="mb-4">
                        <Card className="h-100">
                            <Card.Img
                                variant="top"
                                src={`http://localhost:9000/images/${product.image}`} // image는 DB 컬럼명
                                alt={product.name}
                                style={{width:'100%', height:'200px', objectFit:'cover'}} />
                        </Card>
                    </Col>
                ))}
            </Row>
        </Container>

 

<적용결과>

 

ProductList.js에서 수정한 부분(with. VS Code)
        <Container className="my-4">
            <h1>상품 목록 페이지</h1>
            <Row>
                {products.map((product) => (
                    <Col key={product.id} md={3} className="mb-3">
                        <Card className="h-100">
                            <Card.Img
                                variant="top"
                                src={`http://localhost:9000/images/${product.image}`} // image는 DB 컬럼명
                                alt={product.name}
                                style={{width:'100%', height:'200px', objectFit:'cover'}} />
                            <Card.Body>
                                <Card.Title>{product.name}</Card.Title>
                                <Card.Text>가격 : {product.price}</Card.Text>
                                <Link to={``}>
                                    <Button variant="info" className="me-2">상세 보기</Button>
                                </Link>
                                <Link to={``}>
                                    <Button variant="warning" className="me-2">수정</Button>
                                </Link>
                                    <Button variant="danger" className="me-2">삭제</Button>
                            </Card.Body>
                        </Card>
                    </Col>
                ))}
            </Row>
        </Container>

 

<적용결과>