today_is

[ spring ] 간단한 POS 구현 본문

spring

[ spring ] 간단한 POS 구현

ye_rang 2024. 1. 19. 09:36

오늘의 목표

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)

데이터 수정/삭제/추가 이후 결과와 상관없이 바로 리스트 페이지로 이동시키는 것도 아쉽다. 콘솔창에만 결과를 표시했기 때문에 사용자는 자신의 요청이 성공했는지 모른 채 리스트 페이지로 이동된다.

 

사용자에게 좀 더 친절한 사이트를 제공하기 위해, 이러한 부분들을 개선하고 로직을 더 깊이 있게 구성해야겠다.