CRUD 기능 ArrayList → JPA (MySQL) 및 Model → REST API 변환하기
- 이전 Model 사용 Controller
-
@Controller @RequiredArgsConstructor public class ProductController { final ProductRepository productRepository; // 전체 상품 출력 @GetMapping("/") public String main(Model model) { List<Product> productList = productRepository.findAll(); model.addAttribute("productList", productList); return "productList"; } // 상품 추가 폼 출력 @GetMapping("/add") public String viewAddForm() { return "addProductForm"; } // 상품 추가 @PostMapping("/add-action") public String addProduct(@RequestParam String name, @RequestParam int price, @RequestParam LocalDate limitDate) { Product product = Product.builder() .name(name) .price(price) .limitDate(limitDate) .build(); productRepository.save(product); return "redirect:/"; } // 상품 수정 폼 출력 @GetMapping("/edit") public String viewUpdateForm(@RequestParam Long id, Model model) { Product product = productRepository.findById(id).orElse(null); model.addAttribute("product", product); return "editProductForm"; } // 상품 수정 @PostMapping("/edit-action") public String editProduct(@RequestParam Long id, @RequestParam String name, @RequestParam int price, @RequestParam LocalDate limitDate) { Product product = productRepository.findById(id).orElse(null); product.setName(name); product.setPrice(price); product.setLimitDate(limitDate); productRepository.save(product); return "redirect:/"; } // 상품 삭제 @GetMapping("/delete-action") public String deleteProduct(@RequestParam Long id) { Product product = productRepository.findById(id).orElse(null); productRepository.delete(product); return "redirect:/"; } }
-
- 폼 출력하는 ViewController 별도 생성
-
@Controller public class ProductViewController { // 메인 폼 출력 @GetMapping("/") public String viewMain() { return "productList"; } // 상품 추가 폼 출력 @GetMapping("/add") public String viewAddForm() { return "addProductForm"; } // 상품 수정 폼 출력 @GetMapping("/edit") public String viewEditForm() { return "editProductForm"; } }
-
- DTO 생성
- 엔티티 DTO 생성
- 엔티티 → DTO로 변환하는 메소드도 함께 넣어준다.
- 예시 (ProductDTO)
-
@Getter @Setter @AllArgsConstructor @NoArgsConstructor @Builder public class ProductDTO { private Long id; private String name; private int price; @DateTimeFormat(pattern = "yyyy-mm-dd") private LocalDate limitDate; // DTO-> Entity 변환 메소드 public Product toProductEntity() { return Product.builder() .id(id) .name(name) .price(price) .limitDate(limitDate) .build(); } }
-
- @RequestBody 또는 @ResponseBody로 값을 받을 DTO 생성
- html에서 보내거나 받아올 json 값을 넣어줄 DTO를 생성해준다.
- Request, Response 값 둘 다 DTO를 생성해서 받아준다.
- 받아올 값이 1개라면 DTO 생성없이 Map을 사용해도 괜찮다.
- 예시 (AddReqDTO)
-
@Data public class AddReqDTO { private String name; private int price; @DateTimeFormat(pattern = "yyyy-mm-dd") private LocalDate limitDate; }
- 엔티티 DTO 생성
- 엔티티 클래스에 엔티티 → DTO 변환 메소드 추가
- 위에 DTO 클래스에서 DTO → 엔티티 변환 메소드를 만든것 처럼, 엔티티 클래스에도 엔티티 → DTO 변환 메소드를 추가해준다.
- 예시 (Product)
-
// Entity -> DTO 변환 메소드 public ProductDTO toProductDTO() { return ProductDTO.builder() .id(id) .name(name) .price(price) .limitDate(limitDate) .build(); }
-
- Repository 생성
- JPA를 사용하기 위해 Repository 인터페이스를 생성한다.
- @Repository 어노테이션 주입 후 JpaRepository 상속받기 (extends)
- 예시 (ProductRepository)
-
@Repository public interface ProductRepository extends JpaRepository<Product, Long> { }
-
- ApiController 생성
- 어노테이션 주입
- @RestController
- @RequiredArgsConstructor
- Repository 생성자 주입
- final ProductRepository productRepository;
- CRUD 기능 REST API 생성
- 전체 조회
-
// 상품 전체 조회 @PostMapping("/") public List<Product> main() { List<Product> productList = productRepository.findAll(); return productList; }
-
- 추가
-
// 상품 추가 @PostMapping("/add-action") public List<Product> add(@RequestBody AddReqDTO addReqDTO) { Product product = Product.builder() .name(addReqDTO.getName()) .price(addReqDTO.getPrice()) .limitDate(addReqDTO.getLimitDate()) .build(); productRepository.save(product); List<Product> productList = productRepository.findAll(); return productList; }
-
- 상세 조회
-
// 수정할 상품 불러오기 @PostMapping("/edit") public ProductDTO viewEditForm(@RequestBody Map<String, Long> idMap) { Long id = idMap.get("id"); Product product = productRepository.findById(id).orElse(null); ProductDTO productDTO = product.toProductDTO(); return productDTO; }
-
- 수정
-
// 상품 수정 @PostMapping("/edit-action") public List<Product> updateProduct(@RequestBody ProductDTO productDTO) { Product product = productRepository.findById(productDTO.getId()).orElse(null); product.updateProduct(productDTO.getName(), productDTO.getPrice(),productDTO.getLimitDate()); productRepository.save(product); List<Product> productList = productRepository.findAll(); return productList; }
-
- 삭제
-
// 상품 삭제 @PostMapping("/delete") public List<Product> delete(@RequestBody Map<String, Long> idMap) { Long id = idMap.get("id"); Product product = productRepository.findById(id).orElse(null); productRepository.delete(product); List<Product> productList = productRepository.findAll(); return productList; }
-
- 전체 조회
- 어노테이션 주입
- html에서 fetch()를 사용하여 json 값 전달 및 가져오기
- 전체 조회 (productList.html)
-
...생략... <!-- 상품 목록 --> <div class="list-container"> <div id="productList" class="list"></div> </div> <!-- 총 상품 갯수 --> <div style="margin-bottom: 10px" class="listSizeBox bg-white rounded-start text-danger fw-bold"> <span id="total">▲ 총</span> <span id="productCount" class="text-dark">0</span> <span id="totalNum">개의 상품이 있습니다.</span> </div> ...생략... <script> // 페이지 로드 시 초기 데이터 로드 window.onload = function() { loadData(); }; // 데이터 로드 함수 function loadData() { fetch("/", { method: "POST", headers: { "Content-Type": "application/json" } }) .then(response => response.json()) .then(data => { renderProductList(data); }) .catch(error => console.error('Error:', error)); } // 상품 목록 렌더링 함수 function renderProductList(products) { const productList = document.getElementById('productList'); productList.innerHTML = ''; if (products.length === 0) { productList.innerHTML = ` <div class="row"> <table class="table table-bordered border-danger"> <tr class="text-center"> <th scope="col">상품 목록이 비어있습니다.</th> </tr> </table> </div> `; } else { productList.innerHTML = ` <table class="table table-bordered border-danger"> <tr class="text-center"> <th scope="col" id="listNum" class="text-danger">번호</th> <th scope="col" id="listName">상품명</th> <th scope="col" id="listPrice">가격</th> <th scope="col" id="listDate">유통기한</th> <th scope="col" id="editTitle">수정</th> <th scope="col" id="removeTitle">삭제</th> </tr> ${products.map((product) => ` <tr class="text-center align-middle"> <td>${product.id}</td> <td>${product.name}</td> <td>${product.price}</td> <td>${product.limitDate}</td> <td> <a href="/edit?id=${product.id}" class="btn btn-primary fw-bold editBtn" role="button">수정</a> </td> <td> <button class="btn btn-danger fw-bold removeBtn" onclick="confirmAndDelete(${product.id})">삭제</button> </td> </tr> `).join('')} </table> `; } // 총 상품 갯수 업데이트 document.getElementById('productCount').textContent = products.length; }
-
- 추가 (addProductForm.html)
-
...생략... <form name="addForm" id="addForm"> <!-- 상품명 --> <div class="form-group"> <label for="inputName" class="text-white">상품명:</label> <input type="text" class="form-control" id="inputName" name="inputName" required /> </div> <!-- 가격 --> <div class="form-group"> <label for="inputPrice" class="text-white">가격:</label> <input type="number" class="form-control" id="inputPrice" name="inputPrice" required /> </div> <!-- 유통기한 --> <div class="form-group"> <label for="inputLimitDate" class="text-white">유통기한:</label> <input type="text" class="form-control datepicker" id="inputLimitDate" name="inputLimitDate" required /> </div> <!-- 상품 추가 --> <div class="addButton"> <button type="button" class="btn btn-dark" onclick="productAdded()"> 상품 추가 완료! </button> <button type="button" onclick="history.back();" class="btn btn-light" > 돌아가기 </button> </div> ...생략... <script> const productAdded = () => { const inputName = document.getElementById("inputName").value; const inputPrice = document.getElementById("inputPrice").value; const inputLimitDate = document.getElementById("inputLimitDate").value; fetch("/add-action", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: inputName, price: inputPrice, limitDate: inputLimitDate }) }) .then(response => { if (response.ok) { alert("상품을 추가하였습니다!"); window.location.href = "/"; } else { console.error("상품 추가 실패"); } }) .catch(error => console.error("Error: ", error)); }
-
- 상세 조회 (editProductForm.html)
-
...생략... <form name="formUpdate" id="formUpdate"> <div class="form-group"> <!-- 인덱스 생성 --> <input type="hidden" id="id" name="id"/> <!-- 상품명 --> <label for="inputName" class="text-white">상품명:</label> <input type="text" class="form-control" id="inputName" name="inputName" /> </div> <!-- 가격 --> <div class="form-group"> <label for="inputPrice" class="text-white">가격:</label> <input type="number" class="form-control" id="inputPrice" name="inputPrice" /> </div> <!-- 유통기한 --> <div class="form-group"> <label for="inputLimitDate" class="text-white">유통기한:</label> <input type="text" class="form-control datepicker" id="inputLimitDate" name="inputLimitDate" /> </div> ...생략... <script> // url에서 id 값 가져오기 window.onload = function() { const queryString = window.location.search; const urlParams = new URLSearchParams(queryString); const id = urlParams.get('id'); loadData(id); }; const loadData = (id) => { fetch("/edit", { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({ id: id }) }) .then(respnse => respnse.json()) .then(data => { document.getElementById("id").value = data.id; document.getElementById("inputName").value = data.name; document.getElementById("inputPrice").value = data.price; document.getElementById("inputLimitDate").value = data.limitDate; }) .catch(error => { console.error("Error: ", error); }) }
-
- 수정 (editProductForm.html)
-
...생략... <button type="button" class="btn btn-dark" onclick="updateProduct()" >상품 수정 완료! </button> ...생략... <script> const updateProduct = () => { const id = document.getElementById("id").value; const inputName = document.getElementById("inputName").value; const inputPrice = document.getElementById("inputPrice").value; const inputLimitDate = document.getElementById("inputLimitDate").value; fetch("/edit-action", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: id, name: inputName, price: inputPrice, limitDate: inputLimitDate }) }) .then(response => { if (response.ok) { alert("상품을 수정하였습니다!") window.location.href = "/"; } else { console.error("상품 수정 실패"); } }) .catch(error => { console.error("오류 발생: ", error); }) }
-
- 삭제 (productList.html)
-
...생략... <td> <button class="btn btn-danger fw-bold removeBtn" onclick="confirmAndDelete(${product.id})">삭제</button> </td> ...생략... <script> // 상품 삭제 함수 function confirmAndDelete(id) { var confirmation = confirm("정말로 삭제하시겠습니까?"); if (confirmation) { fetch("/delete", { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id }) }) .then(response => response.json()) .then(data => { renderProductList(data); alert("삭제하였습니다!"); }) .catch(error => console.error('Error:', error)); } }
-
- 전체 조회 (productList.html)
- 기타
- 헷갈렸던 점
- 상세 조회와 수정이 같은 Form에서 이뤄지기 때문에 REST 구현시 조금 헤맸다. (Model 구현은 쉬웠는데..)
- 상세 조회 폼으로 이동하면 먼저 사용자가 선택한 id에 해당하는 값들이 input 박스 안에 출력되어야 하고, 사용자는 출력된 input 박스 값을 수정하여 수정 액션이 이뤄져야 한다.
- 해결 방법
- 수정 Form html으로 가면 사용자가 전체 출력 Form에서 선택한 id를 가져와서 데이터를 출력한다. (localhost:8080/edit?id=1 의 형태에서 id 값만 가져오기!)
- id 값 가져오기
-
window.onload = function() { const queryString = window.location.search; const urlParams = new URLSearchParams(queryString); const id = urlParams.get('id'); loadData(id); };
-
- 데이터 출력하기
-
const loadData = (id) => { fetch("/edit", { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({ id: id }) }) .then(respnse => respnse.json()) .then(data => { document.getElementById("id").value = data.id; document.getElementById("inputName").value = data.name; document.getElementById("inputPrice").value = data.price; document.getElementById("inputLimitDate").value = data.limitDate; }) .catch(error => { console.error("Error: ", error); }) }
-
- id 값 가져오기
- 이후 수정 버튼을 누르면 수정 액션이 진행되도록 한다.
-
const updateProduct = () => { const id = document.getElementById("id").value; const inputName = document.getElementById("inputName").value; const inputPrice = document.getElementById("inputPrice").value; const inputLimitDate = document.getElementById("inputLimitDate").value; fetch("/edit-action", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: id, name: inputName, price: inputPrice, limitDate: inputLimitDate }) }) .then(response => { if (response.ok) { alert("상품을 수정하였습니다!") window.location.href = "/"; } else { console.error("상품 수정 실패"); } }) .catch(error => { console.error("오류 발생: ", error); }) }
-
- 이때 데이터 출력하기와, 수정하기 모두 document.getElementById("id명").value 형식을 사용한다. 나는 데이터 출력은 innerText를 사용하는 줄 알았지만 value를 사용하는 것이였다.
- 수정 Form html으로 가면 사용자가 전체 출력 Form에서 선택한 id를 가져와서 데이터를 출력한다. (localhost:8080/edit?id=1 의 형태에서 id 값만 가져오기!)
- 헷갈렸던 점
html에서 JSP로 변환 실습
- build.gradle 코드 추가
-
dependencies { // JSP implementation 'jakarta.servlet:jakarta.servlet-api' implementation 'jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api' implementation 'org.apache.tomcat.embed:tomcat-embed-jasper' implementation 'org.glassfish.web:jakarta.servlet.jsp.jstl'
-
- application.properties 코드 추가
-
# JSP spring.mvc.view.prefix=/WEB-INF/views/ spring.mvc.view.suffix=.jsp
-
- views 디렉토리에 .jsp 파일 생성
- jsp 파일 상단에 아래 코드를 넣어준다.
-
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ page trimDirectiveWhitespaces="true" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
-
- 이후 구현된 html 코드 전체를 아래에 넣어주고, 타임리프 코드(xmlns:th="http://www.thymeleaf.org")는 지워준다.
- 타임리프로 값을 가져오던 코드는 jsp 문법으로 변경해준다.
- jsp 파일 상단에 아래 코드를 넣어준다.
- dependencies에 타임리프 관련 코드와 기존에 있던 html 파일은 모두 삭제
- 변경 예시
- 전제 조회
- 타임리프
-
<tr th:each="product: ${productList}" class="text-center align-middle"> <th th:text="${product.id}"></th> <td th:text="${product.name}" class="text-center align-middle"></td> <td th:text="${product.price}" class="text-center align-middle"></td> <td th:text="${product.limitDate}" class="text-center align-middle"></td> <td class="text-center align-middle"> <a th:href="@{/edit(id=${product.id})}" class="btn btn-primary fw-bold editBtn" role="button" >수정</a > </td> <td class="text-center align-middle"> <a th:href="@{/delete-action(id=${product.id})}" class="btn btn-danger fw-bold removeBtn" role="button" onclick="return confirmAndNotify(this)" >삭제</a > </td> </tr>
-
- JSP
-
<c:forEach var="product" items="${productList}" varStatus="status"> <tr class="text-center align-middle"> <th><span>${product.id+1}</span></th> <td class="text-center align-middle"><span>${product.name}</span></td> <td class="text-center align-middle"><span>${product.price}</span></td> <td class="text-center align-middle"><span>${product.limitDate}</span></td> <td class="text-center align-middle"> <a href="/edit?id=${product.id}" class="btn btn-primary fw-bold editBtn" role="button" >수정</a > </td> <td class="text-center align-middle"> <a href="/delete-action?id=${product.id}" class="btn btn-danger fw-bold removeBtn" role="button" onclick="return confirmAndNotify(this)" >삭제</a > </td> </tr> </c:forEach>
-
- 타임리프
- 전제 조회
728x90
'교육 (Today I Learned) > Hanaro' 카테고리의 다른 글
[Hanaro] 64일차 / 백엔드 실습 과제 (0) | 2024.04.18 |
---|---|
[Hanaro] 63일차 / Spring Boot (Repository - Service - Controller) (0) | 2024.04.18 |
[Hanaro] 61일차 / Spring Boot (Test, Scheduler) (0) | 2024.04.13 |
[Hanaro] 60일차 / Spring Boot (JPA, HttpSession, Logging) (0) | 2024.04.13 |
[Hanaro] 59일차 / MyBatis (0) | 2024.04.10 |