2024. 10. 9. 22:25ㆍSpring
매장 같은 곳에서 사용하는 포스기들의 시스템을 실제로 비슷하게 구현하여 보자.
1. 테이블로 구성해야 하는 항목
= CRUD 작업 대상
1. 상품 테이블 (Product)
2. 매출 테이블 (Sales)
2. 각 테이블에 대한 스키마 구성
상품 테이블
상품번호 (primary key) |
상품명 | 이미지 | 단가 | 수량 |
NUMBER | VARCHAR2 | VARCHAR2 | NUMBER | NUMBER (default 0) |
매출테이블
매출번호 (primary key) |
날짜 | 상품번호 (foreign key) |
판매수량 |
NUMBER | DATE | NUMBER | NUMBER |
3. 각 테이블에 대해서 구현할 CRUD 기능
상품테이블
기능 | 설명 |
insert | 상품 등록 |
select | 상품 조회 (전체 목록 / 단일 조회) -- 2가지 |
update | 수량 변경 |
delete | 상품 삭제 |
매출테이블
기능 | 설명 |
insert | 매출 등록 |
select | 매출 목록 |
update | 매출 취소 (반품) |
header.jsp
모든 페이지에서 사용할 태그들을 선언해둔다.
이번에는 home 에 링크들을 생성해둘 것이기 때문에
header.jsp 에는 자주 사용하는 스타일과 태그만 작성해 줄 것이다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<c:set var="cpath" value="${pageContext.request.contextPath }" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style>
.frame {
width: 900px;
justify-content: center;
}
.flex {
display: flex;
}
.bold {
font-weight: bold;
}
</style>
</head>
<body>
home.jsp
<%@ include file="header.jsp" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>상품 매출 관리</h1>
<hr>
<h3>오늘은 <fmt:formatDate value="${today }" pattern="yyyy년 MM월 dd일" /> 입니다</h3>
<ul>
<li><a href="${cpath }/product/list">상품 목록</a>
<li><a href="${cpath }/product/add">상품 추가</a>
<li><a href="${cpath }/sales/list">매출 목록</a>
</ul>
</body>
</html>
ProductDTO
import org.springframework.web.multipart.MultipartFile;
public class ProductDTO {
// PRODUCT 테이블
// 이름 널? 유형
// ----- -------- --------------
// IDX NOT NULL NUMBER
// NAME NOT NULL VARCHAR2(500)
// IMG VARCHAR2(1000)
// PRICE NOT NULL NUMBER
// COUNT NUMBER
// SAVE_IMG
private int idx;
private String name;
private String img;
private int price;
private int count;
// UUID로 변경한 값
private String save_img;
// 파일 업로드
private MultipartFile upload;
public String getSave_img() {
return save_img;
}
public void setSave_img(String save_img) {
this.save_img = save_img;
}
public MultipartFile getUpload() {
return upload;
}
public void setUpload(MultipartFile upload) {
this.upload = upload;
}
public int getIdx() {
return idx;
}
public void setIdx(int idx) {
this.idx = idx;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getImg() {
return img;
}
public void setImg(String img) {
this.img = img;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
SalesDTO
Join 사용해서 다른 테이블에 있는 필드의 내용을 가져왔을때에는
결과물의 필드명과 일치해야함으로, DTO에만 필드를 하나 추가하자.
-> private String s_name;
import java.sql.Date;
public class SalesDTO {
// Sales 테이블
// 이름 널? 유형
// ------------- -------- ------
// S_IDX NOT NULL NUMBER
// S_DATE DATE
// S_PRODUCT_IDX NUMBER
// S_COUNT NOT NULL NUMBER
// S_DELETE NUMBER
private int s_idx;
private Date s_date;
private int s_product_idx;
private int s_count;
private int s_delete;
// join 사용시에 쓰일 s_name (상품명)
private String s_name;
public String getS_name() {
return s_name;
}
public void setS_name(String s_name) {
this.s_name = s_name;
}
public int getS_idx() {
return s_idx;
}
public void setS_idx(int s_idx) {
this.s_idx = s_idx;
}
public Date getS_date() {
return s_date;
}
public void setS_date(Date s_date) {
this.s_date = s_date;
}
public int getS_product_idx() {
return s_product_idx;
}
public void setS_product_idx(int s_product_idx) {
this.s_product_idx = s_product_idx;
}
public int getS_count() {
return s_count;
}
public void setS_count(int s_count) {
this.s_count = s_count;
}
public int getS_delete() {
return s_delete;
}
public void setS_delete(int s_delete) {
this.s_delete = s_delete;
}
}
ProductController
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import com.itbank.model.ProductDTO;
import com.itbank.service.ProductService;
@Controller
@RequestMapping("/product")
public class ProductController {
@Autowired private ProductService service;
@GetMapping("/list")
public ModelAndView list() {
ModelAndView mav = new ModelAndView("/product/list");
List<ProductDTO> list = service.getList();
mav.addObject("list", list);
return mav;
}
@GetMapping("/add")
public void add() {}
@PostMapping("/add")
public String product_add(ProductDTO dto) {
int row = service.add(dto);
System.out.println(row != 0 ? "상품 등록 성공" : "상품 등록 실패");
return "redirect:/product/list";
}
@GetMapping("/update/{idx}")
public ModelAndView update(@PathVariable("idx") int idx) {
ModelAndView mav = new ModelAndView("/product/update");
ProductDTO dto = service.getIdx(idx);
mav.addObject("dto", dto);
return mav;
}
@PostMapping("/update/{idx}")
public String update(ProductDTO dto) {
int row = service.update(dto);
System.out.println(row != 0 ? "수정 성공" : "수정 실패");
return "redirect:/product/list";
}
@GetMapping("/delete/{idx}")
public String delete(@PathVariable("idx") int idx) {
int row = service.delete(idx);
System.out.println(row != 0 ? "삭제 성공" : "삭제 실패");
return "redirect:/product/list";
}
}
ProductService
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.itbank.model.ProductDTO;
import com.itbank.repository.ProductDAO;
@Service
public class ProductService {
@Autowired private ProductDAO dao;
private String saveDirectory = "C:\\upload";
public ProductService() {
File dir = new File(saveDirectory);
if(dir.exists() == false) {
dir.mkdirs();
}
}
public List<ProductDTO> getList() {
return dao.selectList();
}
public int add(ProductDTO dto) {
String originalFileName1 = dto.getUpload().getOriginalFilename();
// 파일 확장자만 출력하기 마지막 .의 위치부터 끝까지 잘라냄
String ext1 = originalFileName1.substring(originalFileName1.lastIndexOf("."));
// 새로 저장될 이름은 중복되지 않도록 UUID 를 사용
String storedFileName1 = UUID.randomUUID().toString().replace("-", "");
storedFileName1 += ext1;
File f1 = new File(saveDirectory, storedFileName1);
try {
dto.getUpload().transferTo(f1);
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
}
dto.setImg(originalFileName1);
dto.setSave_img(storedFileName1);
return dao.insertFile(dto);
}
public int update(ProductDTO dto) {
return dao.update(dto);
}
public ProductDTO getIdx(int idx) {
return dao.SelectOne(idx);
}
public int delete(int idx) {
return dao.delete(idx);
}
}
ProductDAO
import java.util.List;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import com.itbank.model.ProductDTO;
public interface ProductDAO {
@Select("select * from product1 order by idx desc")
List<ProductDTO> selectList();
@Insert("insert into product1(name, img, price, save_img) values(#{name}, #{img}, #{price}, #{save_img})")
int insertFile(ProductDTO dto);
@Update("update product1 set count = #{count} where idx = #{idx}")
int update(ProductDTO dto);
@Select("select * from product1 where idx = #{idx}")
ProductDTO SelectOne(int idx);
@Delete("delete from product1 where idx = #{idx}")
int delete(int idx);
}
list.jsp
전체 상품 목록
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ include file="../header.jsp" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>상품 목록</h1>
<div class="frame productList">
<c:forEach var="dto" items="${list }">
<div class="flex box">
<img src="${cpath }/upload/${dto.save_img}" height="250px">
<p class="bold">${dto.name }</p>
<p class="sp">${dto.price }</p>
<p class="sp">${dto.count }</p>
<p><a href="${cpath }/product/update/${dto.idx}"><button>수량 변경</button></a>
<p><a href="${cpath }/product/delete/${dto.idx}"><button>삭제</button></a>
</div>
</c:forEach>
</div>
</body>
</html>
코드 풀이
ProductController 를 보면, ModelAndView 객체가 list 라는 이름으로 저장됨.
-> mav.addObject("list", list)
이때, list 는 ProductService 와 ProductDAO 를 거쳐서 sql 문을 만나게 되고, 그의 결과가 list로 넘어온다.
-> select * from product1 order by idx desc
list 라는 mav 객체는 product/list.jsp 에서 EL태그를 이용하여 사용할 수 있으며,
이를 반복문으로 출력한다면 모든 목록이 나오게 된다.
-> <c:forEach var="dto" items="${list }">
add.jsp
상품 등록
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ include file="../header.jsp" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>상품 추가</h1>
<hr>
<form method="POST" enctype="multipart/form-data">
<p><input type="text" name="name" placeholder="상품명" required autofocus></p>
<p><input type="file" name="upload"></p>
<p><input type="number" name="price" placeholder="가격" required></p>
<p><input type="submit" value="상품 등록"></p>
</form>
</body>
</html>
코드 풀이
form 을 제출하면, method POST로 인하여, ProductController 에 있는 PostMapping이 실행된다.
-> @PostMapping("/add")
이미지도 추가했다면, ProductService 에 있는 add() 함수에 의하여, 원본 파일명의 확장자만 떼어내서 새로운 변수에 저장하고,
-> String ext1 = originalFileName1.substring(originalFileName1.lastIndexOf("."));
UUID 를 이용한 새로운 파일명을 만들어내서 확장자를 더한다. (파일명 중복을 피하기 위함)
-> String storedFileName1 = UUID.randomUUID().toString().replace("-", "");
storedFileName1 += ext1;
미리 선언한 saveDirectory 를 이용하여, 해당 디렉토리에 바뀐 파일명으로 생성한 이미지를 저장한다.
-> File f1 = new File(saveDirectory, storedFileName1);
dto.getUpload().transferTo(f1);
DB의 내용도 변경하기 위해 setter 를 이용한다.
(DB를 통해서 원본파일명과 바뀐 파일명을 알수 있음)
-> dto.setImg(originalFileName1);
dto.setSave_img(storedFileName1);
ProductDAO 를 거쳐서 sql문을 통하여, insert 가 이루어진다.
-> @Insert("insert into product1(name, img, price, save_img) values(#{name}, #{img}, #{price}, #{save_img})")
insert 의 결과를 콘솔창을 통해 확인할 수 있으며,
-> System.out.println(row != 0 ? "상품 등록 성공" : "상품 등록 실패");
성공여부와 관계없이 redirect 를 이용하여 list 페이지로 이동된다.
-> return "redirect:/product/list";
update.jsp
상품 수량 수정
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ include file="../header.jsp" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>상품 수량 수정</h1>
<hr>
<form method="POST">
<p><input type="text" name="name" value="${dto.name }"></p>
<p><input type="number" min="0" max="500" step="1" name="count" value="${dto.count }"></p>
<p><input type="submit" value="수정"></p>
<input type="hidden" name="idx" value="${dto.idx }">
</form>
</body>
</html>
코드 풀이
list 에서 수량변경 버튼을 누르면 해당 상품의 idx 를 포함한채로 update.jsp 로 넘어오게 된다.
-> @GetMapping("/update/{idx}")
form을 제출하면 ProductController 에 있는 PostMapping 이 실행된다.
-> @PostMapping("/update/{idx}")
ProductDAO 를 거쳐서 sql문이 수행된다.
-> @Update("update product1 set count = #{count} where idx = #{idx}")
sql 문 실행결과를 콘솔창에 띄우고,
-> System.out.println(row != 0 ? "수정 성공" : "수정 실패");
실행결과와는 상관없이 redirect 를 이용하여 list 페이지로 이동.
-> return "redirect:/product/list";
delete.jsp
상품 삭제
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ include file="../header.jsp" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
</body>
</html>
사용자에게 보여줄 필요가 없기 때문에 아무 코드도 존재하지 않는다.
코드 풀이
list.jsp 에서 상품삭제 버튼을 누르면 상품의 idx 와 함께 delete.jsp로 넘어온다.
-> <p><a href="${cpath }/product/delete/${dto.idx}"><button>삭제</button></a>
Controller , Service , DAO 를 거쳐서 sql 문을 만난다.
-> @Delete("delete from product1 where idx = #{idx}")
sql문의 결과를 콘솔창에 띄운다.
-> System.out.println(row != 0 ? "삭제 성공" : "삭제 실패");
결과와는 상관없이 redirect 를 이용하여 list 로 이동.
-> return "redirect:/product/list";
글을 작성하며 다시 코드들을 살펴보니 몇 가지 수정하고 싶은 부분이 존재했다.
먼저, delete.jsp에서 아무 코드도 작성하지 않고 그냥 둘거면
차라리 alert창이라도 띄우게 해서 사용자가 이에 대한 요청을 확인할 수 있게 했으면 싶었다.
그리고 데이터 수정, 추가, 삭제 후 결과와 상관없이 바로 list.jsp로 이동 시킨 것도 조금 아쉬웠다.
사용자의 관점으로 봤을때는 이런 사소한 부분이 조금 불편하게 느껴질 것 같았고, 다음에는 좀 더
이러한 사소한 부분도 사용자의 관점에서 볼 수 있도록 신경써야 할 것 같다.
'Spring' 카테고리의 다른 글
[Spring] WebSocket을 활용한 메모장 만들기 (2) | 2024.10.10 |
---|---|
[Spring] HashMap json mapping (2) | 2024.10.10 |
[Spring] RestController (1) | 2024.10.09 |
[Spring] 설문 투표 및 결과 보기 (1) | 2024.10.09 |
[Spring] FileComponent (1) | 2024.10.09 |