today_is
[ spring ] 간단한 POS 구현 본문
오늘의 목표
product 테이블을 만들어서 간단하게 상품관리, 매출 등을 볼 수 있는 페이지를 만들어보자
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
: 링크 생성
<%@ 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>
<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
package com.itbank.model;
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;
package com.itbank.model;
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
package com.itbank.controller;
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
package com.itbank.service;
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
package com.itbank.repository;
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";
Study_review
블로그를 작성하기 위해 코드들을 다시 보니 아쉬운 점들이 보인다.
아쉬운점 1 )
먼저, delete.jsp에 아무런 스타일도 적용하지 않고 빈 페이지로 두었는데, 그렇게 할 바에야 왜 사용했는지 의문이 든다.
차라리 삭제를 확인하는 alert 창을 띄워 "삭제하시겠습니까?"라는 안내와 함께 사용자의 요청을 한 번 더 확인받는 것이 더 나을 것 같다.
아쉬운점 2)
데이터 수정/삭제/추가 이후 결과와 상관없이 바로 리스트 페이지로 이동시키는 것도 아쉽다. 콘솔창에만 결과를 표시했기 때문에 사용자는 자신의 요청이 성공했는지 모른 채 리스트 페이지로 이동된다.
사용자에게 좀 더 친절한 사이트를 제공하기 위해, 이러한 부분들을 개선하고 로직을 더 깊이 있게 구성해야겠다.
'spring' 카테고리의 다른 글
[ json ] HashMap 을 이용한 json 데이터 mapping (0) | 2024.02.04 |
---|---|
[ Exception ] 예외처리, 예외전가 (0) | 2024.01.30 |
[ spring ] RestController (0) | 2024.01.16 |
[ spring ] 설문 및 투표 + 설문결과보기 기능 구현 (0) | 2024.01.09 |
[ spring ] FileComponent , 다중 파일업로드 (0) | 2024.01.04 |