[Spring] 매장 포스기 시스템

2024. 10. 9. 22:25Spring

매장 같은 곳에서 사용하는 포스기들의 시스템을 실제로 비슷하게 구현하여 보자.


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