React+Spring Boot

상품 추가 기능

작성자
vita
작성일
2023-11-19 21:59
조회
328

상품 추가 기능

상품 등록의 전반적인 흐름은 아래와 같습니다.

 

1. React의 ListProduct.js에서 [상품등록] 버튼을 누르면 http://localhost:3000/write 주소를 요청합니다.

2. WriteProduct.js 로 이동합니다.

3. WriteProduct.js 에서 내용을 작성한 후 서버의 Controller로 보냅니다.

4. Controller에서 받은 입력값을 DAO에 보냅니다.

5. DAO는 mapper에 자료를 전달하여 테이블의 레코드가 추가됩니다.

6. 서버에서 처리가 완료되면 React의 ListProduct.js가 실행되고 최신 상품목록을 받아와서 화면에 출력합니다.

 

1. WriteProduct.js (React)

파일 위치: c:/react/frontend/src/components/WriteProduct.js

import React, { useRef } from 'react';

import { useNavigate } from 'react-router';

import './main.css';

function WriteProduct() {

  const navigate = useNavigate();

  const product_name = useRef();

  const price = useRef();

  const description = useRef();

  const img = useRef();

 

  return (

    <>

      <h2>상품 정보 등록</h2>

      <table>

        <tbody>

          <tr>

            <td>상품명</td>

            <td><input ref={product_name} /></td>

          </tr>

          <tr>

            <td>가격</td>

            <td><input type='number' ref={price} /></td>

          </tr>

 

          <tr>

            <td>상품설명</td>

            <td><textarea rows='5' cols='60' ref={description} />

            </td>

          </tr>

          <tr>

            <td>상품이미지</td>

            <td>

              <input type='file' ref={img} />

            </td>

          </tr>

          <tr>

            <td colSpan='2' align='center'>

              <button type='button' onClick={() => {

             const form = new FormData();

             form.append('product_name', product_name.current.value);

             form.append('price', price.current.value);

             form.append('description', description.current.value);

             form.append('img', img.current.files[0]);

             fetch('/insert', {

               method: 'post',

               encType: 'multipart/form-data',

               body: form

             }).then(() => {

               navigate('/');

             });

           }}>확인</button>

              <button onClick={() => navigate('/')}>목록</button>

            </td>

          </tr>

        </tbody>

      </table>

    </>

  );

};

export default WriteProduct;

 

[해설]

 

  const navigate = useNavigate();

 

상품 등록이 완료된 후 상품목록으로 url을 이동시키기 위해 필요한 기능입니다.

 

  const product_name = useRef();

 

useRef() 함수는 태그에 입력한 값을 읽기 위한 함수입니다.

 

<input ref={product_name} />

 

ref는 useRef() 함수가 참조하는 태그의 이름입니다.

 

 

<td><input ref={product_name} /></td>

 

input 태그는 서버에 보낼 텍스트를 입력할 때 사용하는 태그입니다.

 

한줄이 아니라 여러줄의 텍스트를 입력하려면 textarea 태그를 사용합니다.

textarea 태그의 형식은 아래와 같습니다.

 

<textarea rows="행의 수" cols="열의 수" >텍스트의 내용</textarea>

 

 

<input type='file' ref={img} />

 

서버에 파일을 보내기 위해서는 <input type="file">을 사용합니다.

 

확인 버튼을 누르면 폼 양식에 입력한 값들을 서버에 보내게 됩니다.

 

              fetch('/insert', {

                method: 'post',

                encType: 'multipart/form-data',

                  body: form

              })

 

method는 대표적으로 get과 post가 있습니다.

get은 주로 자료를 요청할 때 사용합니다.  post는 서버에 데이터를 보낼 때 사용합니다. 여기서는 서버에 자료를 보내야 하므로 post를 사용했습니다.

파일 첨부를 하려면 enctype="multipart/form-data" 옵션이 꼭 필요합니다.  이 옵션이 없으면 파일 첨부가 되지 않습니다.

 

<button onClick={() => navigate('/')}>목록</button>

 

[목록] 버튼을 누르면 http://localhost/list 로 다시 이동하게 됩니다.    

 


 







2. ListProduct.js (React)

파일 위치: c:/react/frontend/src/components/ListProduct.js

import React, { useRef, useEffect, useState } from 'react';

import './main.css';

import { useNavigate } from 'react-router';

import ProductItem from './ProductItem';

function ListProduct() {

  const navigate = useNavigate();

  const [items, setProductList] = useState([]);

[생략]

  return (

    <>

      <h2>상품목록</h2>

      <button onClick={() =>navigate('/write')}>상품등록</button>

      <hr />

      등록된 상품수: {items.length}

[생략]

    </>

  );

};

export default ListProduct;

 

[해설]

 

<button onClick={() => navigate('/write')}>상품등록</button>

 

버튼을 누르면 http://localhost/write 으로 이동하게 됩니다.  onclick은 버튼을 누를 때 실행한다는 뜻입니다.  

 

3. App.js (React)

파일 위치: c:/react/frontend/src/App.js

import React from 'react';

import { Routes, Route } from 'react-router';

import { BrowserRouter } from 'react-router-dom';

 

import ListProduct from './components/ListProduct';

import WriteProduct from './components/WriteProduct';

 

function App() {

  console.warn = function no_console() { };

  return (

    <>

      <BrowserRouter>

        <Routes>

          <Route path='/' element={<ListProduct />} />

          <Route path='/write' element={<WriteProduct />} />

          <Route path='*' element={<ListProduct />} />

        </Routes>

      </BrowserRouter>

    </>

  );

}

 

export default App;

 

[해설]

 

<Route path='/write' element={<WriteProduct />} />

<Route path='*' element={<ListProduct />} />

 

/write 을 클릭하면 WriteProduct.js 컴포넌트를 화면에 출력합니다.

path=’*’ 는 그 외의 나머지 url을 입력하면 ListProduct.js 로 이동한다는 것입니다.

 

4. product.xml (Spring Boot)

 

<insert> 태그를 작성합니다.

 

파일 위치: src/main/resources/mappers/product.xml

[생략]

<mapper namespace="product">

    <select id="list" resultType="java.util.Map">

        select * from product

        order by product_name

    </select>

    <insert id="insert">

        insert into product (product_name,description, price, filename) 

        values

        (#{product_name},#{description},#{price}, #{filename})

    </insert>

 

</mapper>    

 

 

[해설]

 

insert 태그에는 아래와 같이 parameterType 이라는 속성이 생략되어 있습니다.  레코드를 저장하려면 전달되는 변수의 자료형이 필요합니다. 여기서는 map이 전달되었습니다.

 

<insert id="insert" parameterType="java.util.Map">

 

map에 저장된 product_name, description, price, filename 변수들이 매핑되어 레코드가 추가됩니다.

리액트 페이지에서 스프링의 Controller로, Controller에서 dao로, dao에서 mapper로 자료가 어떻게 전달되는지 잘 살펴보시기 바랍니다.

리액트 페이지에서 입력한 값들은 아래와 같습니다. 여기서 name에 지정한 변수명을 잘 확인하시기 바랍니다.  이 변수명을 테이블의 필드명과 동일하게 지정해야 정확히 매핑이 됩니다.

 

const form = new FormData();

form.append('product_name', product_name.current.value);

form.append('price', price.current.value);

form.append('description', description.current.value);

form.append('img', img.current.files[0]);

 

product_name, price, description 변수는 Controller의 Map에 전달이 되어 저장됩니다.

 

public String insert(@RequestParam Map<String, Object> map, @RequestParam MultipartFile img, HttpServletRequest request)

 

폼에 입력한 값들이 맵에 저장되는 과정이 내부적으로 자동으로 처리됩니다.  

 

map.put("product_name", product_name);

map.put("price", price);

map.put("description", description);

 

첨부파일은 MultipartFile에 전달되고 파일은 디렉토리에 저장되고 파일이름만 map에 추가됩니다.

 

 

dao의 insert method에 map 데이터가 전달됩니다.

dao는 mapper에 map을 전달합니다.

 

    public void insert(Map<String, Object> map) {

        sqlSession.insert("product.insert", map);

    }

 

mapper에 map 데이터가 전달됩니다.  parameterType은 작성하지 않아도 됩니다.  #{변수명}에는 map에 저장된 변수명을 사용합니다.

 

 

<insert id="insert" parameterType="java.util.Map">

    insert into product

       (product_name, description, price, filename)

    values

    (#{product_name},#{description},#{price},#{filename})

</insert>

 

 

5. ProductDAO.java (Spring Boot)

 

dao에 insert() method를 작성합니다.

 

파일위치: src/main/java/com/example/product/ProductDAO.java

package com.example.product;

import java.util.List;

import java.util.Map;

import org.apache.ibatis.session.SqlSession;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Repository;

 

@Repository

public class ProductDAO {

 

    @Autowired

    SqlSession sqlSession;

    public List<Map<String, Object>> list() {

        return sqlSession.selectList("product.list");

    }

 

    public void insert(Map<String, Object> map) {

        sqlSession.insert("product.insert", map);

    }

}

 

[해설]

 

public void insert(Map<String, Object> map) {

        sqlSession.insert("product.insert", map);

}

 

namespace가 product이고 id가 insert인 태그에 map 데이터가 전달됩니다.

6. ProductController.java (Spring Boot)

 

insert() method를 추가합니다.

 

파일위치: src/main/java/com/example/product/ProductController.java

[생략]

    @RequestMapping("/list")

    public List<Map<String, Object>> list(ModelAndView mav) {

        return productDao.list(product_name);

    }

    @RequestMapping("/insert")

    public void insert(@RequestParam Map<String, Object> map, @RequestParam(required = false) MultipartFile img, HttpServletRequest request) {

        String filename = "-";

        if (img != null && !img.isEmpty()) {

            filename = img.getOriginalFilename();

            try {

                ServletContext application = request.getSession().getServletContext();

                String path = application.getRealPath("/static/images/");

                img.transferTo(new File(path + filename));

            } catch (Exception e) {

                e.printStackTrace();

            }

        }

        map.put("filename", filename);

        productDao.insert(map);

    }

}

 

[해설]

 

@RequestMapping("/insert")

public void insert(@RequestParam Map<String, Object> map, @RequestParam(required = false) MultipartFile img, HttpServletRequest request) {

 

}

 

WriteProduct.js 에서 입력한 값들이 Map에 전달됩니다.

Map의 형식은 아래와 같습니다.

Map<key의 자료형, value의 자료형>

Map<String, Object> 이라고 했으므로 key는 스트링, value는 모든 자료형이라는 뜻입니다.

 

@RequestMapping("/insert")

public void insert(@RequestParam Map<String, Object> map, @RequestParam(required = false) MultipartFile img, HttpServletRequest request) {

 

}

 

첨부 파일은 Map에 저장되지 않으므로 MultipartFile 변수를 별도로 작성해야 합니다.

 

WriteProduct.js 에서 file 태그의 name을 img 라고 했으므로 Controller에서도 img 라고 해야 정확히 전달이 됩니다.

 

form.append('img', img.current.files[0]);

 

@RequestMapping("/insert")

public void insert(@RequestParam Map<String, Object> map, @RequestParam(required = false) MultipartFile img, HttpServletRequest request) {

}

 

HttpServletRequest 자료형은 사용자의 요청 사항을 처리하는 객체입니다.  

 

        String filename = "-";

 

첨부파일이 없는 경우도 있으므로 – 으로 빈값임을 표시했습니다.

 

        if (img != null && !img.isEmpty()) {

 

첨부파일이 null이 아니고 빈값이 아니면

 

            filename = img.getOriginalFilename();

 

파일 이름을 가져옵니다.

 

try {

        ServletContext application = request.getSession().getServletContext();

 

        String path = application.getRealPath("/static/images/");

        img.transferTo(new File(path + filename));

} catch (Exception e) {

        e.printStackTrace();

}

 

Java에서 파일 입출력은 반드시 예외처리를 해야 하므로 try ~ catch 로 처리를 했습니다.

request.getSession().getServletContext(); 명령어로 application 객체를 가져옵니다.  application은 웹서버의 정보를 조회할 수 있는 객체입니다.

application.getRealPath("/static/images/"); 명령어는 현재 실행중인 웹프로젝트의 실제 서비스 경로를 구합니다.

http://localhost/static/images 디렉토리가 서버에 실제로 저장된 물리적인 경로를 구하게 됩니다.

 

        map.put("filename", filename);

 

맵에 filename 변수를 추가합니다.

 

        productDao.insert(map);

 

dao에 자료를 보내서 저장합니다.

 

http://localhost/list 로 이동하면서 화면에 반영이 됩니다.

 

7. 상품 등록 실행 결과 확인

코드가 바뀌면 Spring Boot Project를 재시작을 해야 합니다.

재시작을 하기 전에 현재 실행중인 웹서비스를 꺼야 합니다.  Console 화면에서 빨간색 사각형을 누르면 현재 실행중인 웹서비스가 꺼지게 됩니다.

 


 

다시 프로젝트에서 우클릭을 하고 Run As > spring boot app을 클릭하면 웹서버가 시작됩니다.

Console에 에러가 없으면 정상적으로 실행된 것입니다.

React 웹브라우저를 실행하신 후 주소창에 http://localhost:3000 을 입력하면 상품목록을 볼 수 있습니다.

[상품등록] 버튼을 누르면 상품등록 페이지가 출력됩니다.

 


 

상품 정보를 입력하고 [등록] 버튼을 누르면 테이블에 저장되고 화면에 상품 목록이 출력됩니다.

 


 

 

상품목록은 한 줄에 5개씩 출력됩니다.

 


 

Scroll to Top