상품 상세 기능
상품 상세 기능
상품 상세의 전반적인 흐름은 아래와 같습니다.
ListProduct.js 에서 http://localhost/detail/상품코드 형식으로 만들어진 하이퍼링크를 클릭합니다.
상품코드가 Controller, dao, mapper에 순차적으로 전달됩니다. sql 명령어를 실행하여 상품코드에 해당하는 1개의 레코드를 Controller에 되돌려줍니다.
Controller는 그 자료를 json 형태로 리턴합니다.
React의 DetailProduct에서는 서버에서 받은 json 데이터를 변환하여 화면에 출력합니다.
1. ProductItem.js (React)
파일 위치: c:/react/frontend/src/components/ProductItem.js
import React from 'react';
import { Link } from 'react-router-dom';
import './main.css'
function ProductItem({ product_code, product_name, price, filename }) {
[생략]
return (
<div style={{ margin: '5px' }}>
<span dangerouslySetInnerHTML={{ __html: img }}></span>
<Link to={`/detail/${product_code}`}>
상품명 : {product_name}<br />
가격: {price}원
</Link>
<br /><br />
</div>
)
}
}
export default ProductItem;
[해설]
<Link to={`/detail/${product_code}`}>
상품명 : {product_name}<br />
가격: {price}원
</Link>
상품정보를 클릭하면 상세페이지로 이동하기 위하여 하이퍼링크를 걸었습니다.
<Link> 태그의 형식은 아래와 같습니다.
<Link to=’이동할주소’>텍스트</Link>
이동할 주소는 /detail/${row.product_code} 입니다.
상품코드가 1번이라면 http://localhost/detail/1 로
url이 구성됩니다.
2. DetailProduct.js (React)
파일 위치: c:/react/frontend/src/components/DetailProduct.js
import React, { useRef, useEffect, useState } from 'react';
import './main.css';
import { useNavigate } from 'react-router-dom';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then(response => {
return response.json();
})
.then(data => {
setData(data);
setLoading(false);
})
}, []);
return [data, loading];
}
function DetailProduct() {
const paths = window.location.href.split('/');
const url = '/' + paths[paths.length - 2] + '/' + paths[paths.length - 1];
const [data, loading] = useFetch(url);
const navigate = useNavigate();
const product_name = useRef();
const price = useRef();
const description = useRef();
const img = useRef();
if (loading) {
return (
<div>loading</div>
)
} else {
let src='';
let image_url = '';
if (data.filename !== '-') {
src = `http://localhost/static/images/${data.filename}`;
image_url = `<img src=${src} width='300px' height='300px' />`;
} else {
image_url='';
}
return (
<>
<table>
<tbody>
<tr>
<td>상품명</td>
<td><input ref={product_name} defaultValue={data.product_name} /></td>
</tr>
<tr>
<td>가격</td>
<td><input type='number' ref={price} defaultValue={data.price} /></td>
</tr>
<tr>
<td>상품설명</td>
<td><textarea rows='5' cols='60' ref={description} defaultValue={data.description} /></td>
</tr>
<tr>
<td>상품이미지</td>
<td>
<span dangerouslySetInnerHTML={{ __html: image_url }}></span>
<br />
<input type='file' ref={img} />
</td>
</tr>
<tr>
<td colSpan='2' align='center'>
<button onClick={() => navigate('/')}>목록</button>
</td>
</tr>
</tbody>
</table>
</>
);
}
};
export default DetailProduct;
[해설]
상세 화면은 WriteProduct.js 와 많이 비슷합니다.
<td><input ref={product_name} defaultValue={data.product_name} /></td>
상품이름을 태그에 표시하기 위하여 defaultValue 속성을 추가했습니다.
let src='';
let image_url = '';
if (data.filename !== '-') {
src = `http://localhost/static/images/${data.filename}`;
image_url = `<img src=${src} width='300px' height='300px' />`;
} else {
image_url='';
}
<span dangerouslySetInnerHTML={{ __html: image_url }}></span>
기존에 상품이미지가 업로드된 경우 그 상품이미지를 표시하기 위하여 조건문을 추가했습니다.
3. App.js (React)
App.js 에 DetailProduct 항목을 추가합니다.
파일 위치: 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';
import DetailProduct from './components/DetailProduct';
function App() {
console.warn = function no_console() { }; //경고문 제거
return (
<>
<BrowserRouter>
<Routes>
<Route path='/' element={<ListProduct />} />
<Route path='/write' element={<WriteProduct />} />
<Route path='/detail/:product_code' element={<DetailProduct />} />
<Route path='*' element={<ListProduct />} />
</Routes>
</BrowserRouter>
</>
);
}
export default App;
[해설]
<Route path='/detail/:product_code' element={<DetailProduct />} />
상품을 클릭하면 서버로 /detail/상품코드 형태로 호출이 됩니다.
:product_code 는 변수입니다. DetailProduct.js 에 product_code 라는 변수명으로 상품코드가 전달됩니다.
4. product.xml (Spring Boot)
id가 detail인 태그를 추가합니다.
파일 위치: src/main/resources/mappers/product.xml
<?xml version="1.0" encoding="UTF-8"?>
[생략]
<insert id="insert">
insert into product (product_name, description, price, filename)
values
(#{product_name}, #{description}, #{price}, #{filename})
</insert>
<select id="detail" resultType="java.util.Map">
select * from product
where product_code=#{product_code}
</select>
</mapper>
[해설]
sql 명령어를 실행한 후 map에 자료를 저장하여 dao로 리턴합니다. 상품코드가 일치하는 1개의 레코드가 리턴됩니다.
<select id="detail" resultType="java.util.Map">
select * from product
where product_code=#{product_code}
</select>
5. ProductDAO.java (Spring Boot)
detail() 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(String product_name) {
return sqlSession.selectList("product.list", "%" + product_name + "%");
}
public void insert(Map<String, Object> map) {
sqlSession.insert("product.insert", map);
}
public Map<String, Object> detail(String product_code) {
return sqlSession.selectOne("product.detail", product_code);
}
}
[해설]
public Map<String, Object> detail(String product_code)
Controller에서 전달한 상품코드가 String product_code 변수에 저장됩니다.
return sqlSession.selectOne("product.detail", product_code);
mapper에 상품코드를 전달하여 레코드를 조회합니다.
상품코드는 primary key로 설정되어 있으므로 1개만 리턴됩니다. 레코드가 2개 이상이면 selectList(), 1개이면 selectOne()을 사용합니다.
6. ProductController.java (Spring Boot)
detail() method를 작성합니다.
파일위치: src/main/java/com/example/product/ProductController.java
[생략]
@RestController
public class ProductController {
[생략]
@RequestMapping("/insert")
public void insert(@RequestParam Map<String, Object> map, @Request HttpServletRequest request) {
[생략]
}
@RequestMapping("/detail/{product_code}")
public Map<String, Object> detail(@PathVariable String product_code) {
return productDao.detail(product_code);
}
}
[해설]
@RequestMapping("/detail/{product_code}")
public Map<String, Object> detail(@PathVariable String product_code) {
상세화면을 보기 위한 url의 형식을 아래와 같이 만들었습니다.
http://localhost/detail/{상품코드}
상품코드가 1번이라면 http://localhost/detail/1 이 됩니다.
상품코드는 클릭할 때마다 바뀌므로 변수로 처리가 되어야 합니다. 이 변수는 url에 포함되는 변수이므로 @PathVariable 어노테이션을 붙여서 값을 전달했습니다.
return productDao.detail(product_code);
상품코드에 해당하는 레코드를 dao에 요청합니다.
7. 상품 상세 실행 결과 확인
상품목록에서 상품정보 링크를 클릭하면 해당되는 상품의 상세 정보를 출력합니다.