어제에 이어서 유저가 본인의 옷을 등록/삭제할 수 있는 기능을 추가했다.
어려운 기능이라기 보다...entity의 관계 설정이 너무 어렵다;;
컨트롤러
package org.member.clothController;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.member.ClothDTO;
import org.member.ClothVO;
import org.member.MemberDTO;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Slf4j
@RestController
@RequestMapping
public class ClothController {
public final ClothService clothService;
public ClothController(ClothService clothService){
this.clothService = clothService;
}
//옷 불러오기 메서드
@PostMapping("/selectAllClothes")
public List<ClothDTO> selectAllClothes(HttpSession session){
//전체 옷 가져오기
List<ClothDTO> clothDTOS = clothService.selectAllClothes();
// 현재 로그인한 유저의 옷만 출력
MemberDTO memberDTO = (MemberDTO) session.getAttribute("memberDTO");
List<ClothDTO> memberClothes = new ArrayList<>();
for (ClothDTO clothDTO : clothDTOS) {
if (clothDTO.getUserid().equals(memberDTO.getId())) {
memberClothes.add(clothDTO);
}
}
return memberClothes;
}
//옷 추가하기 메서드
@PostMapping("/updateCloth")
public void updateCloth(ClothDTO updatedCloth, HttpSession session){
MemberDTO member = (MemberDTO) session.getAttribute("memberDTO");
updatedCloth.setUserid(member.getId());
clothService.updateClothes(updatedCloth);
}
//옷 삭제하기 메서드
@PostMapping("/deleteCloth")
public void deleteClothes(Integer id) {
clothService.deleteClothes(id);
}
}
서비스
package org.member.clothController;
import org.member.ClothDTO;
import org.member.ClothVO;
import org.member.MemberVO;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class ClothService {
public final ClothRepository clothRepository;
public ClothService(ClothRepository clothRepository) {
this.clothRepository = clothRepository;
}
//옷 불러오기 메서드
public List<ClothDTO> selectAllClothes() {
List<ClothVO> clothVOList = new ArrayList<>(clothRepository.findAll());
List<ClothDTO> clothDTOList = new ArrayList<>(clothVOList.size());
for (ClothVO clothVO : clothVOList) {
ClothDTO clothDTO = new ClothDTO();
clothDTO.setId(clothVO.getId());
clothDTO.setUserid(clothVO.getMemberVO().getId());
clothDTO.setCategory(clothVO.getCategory());
clothDTO.setClothdata(clothVO.getClothdata());
clothDTOList.add(clothDTO);
}
return clothDTOList;
}
//옷 추가하기 메서드
public void updateClothes(ClothDTO updatedCloth) {
ClothVO clothVO = new ClothVO();
MemberVO memberVO = new MemberVO();
memberVO.setId(updatedCloth.getUserid());
clothVO.setMemberVO(memberVO);
clothVO.setCategory(updatedCloth.getCategory());
clothVO.setClothdata(updatedCloth.getClothdata());
clothRepository.save(clothVO);
}
//옷 삭제하기 메서드
public void deleteClothes(Integer id) {
clothRepository.deleteById(id);
}
}
Repository
package org.member.clothController;
import org.member.ClothVO;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ClothRepository extends JpaRepository<ClothVO, Integer>{
}
Entity
package org.member;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.ColumnDefault;
@Getter
@Setter
@Entity
@Table(name = "clothvo")
public class ClothVO {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@ColumnDefault("nextval('clothvo_clothid_seq'")
@Column(name = "clothid", nullable = false)
private Integer id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "userid")
private MemberVO memberVO;
@Size(max = 50)
@NotNull
@Column(name = "category", nullable = false, length = 50)
private String category;
@Size(max = 100)
@NotNull
@Column(name = "clothdata", nullable = false, length = 100)
private String clothdata;
}
DTO
package org.member;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serializable;
/**
* DTO for {@link ClothVO}
*/
@Data
public class ClothDTO implements Serializable {
@NotNull
Integer userid;
@Size(max = 50)
@NotBlank(message = "카테고리가 지정되어야 합니다.")
String category;
@Size(max = 100)
@NotBlank(message = "옷이 입력되어야 합니다.")
String clothdata;
}
MyClothUpdatePage.jsp
<%@ page import="org.member.MemberVO" %>
<%@ page import="org.member.MemberDTO" %>
<%@ page import="org.member.ClothDTO" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<script type="text/javascript" src="<c:url value="/resources/js/MyClothUpdatePage.js" />"></script>
</head>
<body>
<h1>My Cloth Update Page</h1>
<p>
<div id="allClothesField"></div>
<input type="button" id="showClothField" value="옷 수정하기">
<div id="clothField" style="display: none">
<form id="clothSaveField">
<input type="hidden" id="userid" value="${sessionScope.memberDTO.id}">
<input type="text" id="cloth" name="cloth">
<select id="categoryForCloth">
<option value="모자">모자</option>
<option value="겉옷">겉옷</option>
<option value="상의">상의</option>
<option value="하의">하의</option>
<option value="양말">양말</option>
<option value="신발">신발</option>
</select>
<input type="submit" value="확인">
</form>
</div>
</p>
<form method="post" action="<c:url value="/member/logout"/>">
<input type="submit" value="로그아웃">
</form>
</p>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
crossorigin="anonymous"></script>
</body>
</html>
MyClothUpdatePage.js
$(function(){
// 현재 옷 출력
viewMyClothes()
// 옷 저장 필드 토글
$("#showClothField").on("click", function(){
$("#clothField").toggle();
})// 옷 저장 필드 토글
// 옷 저장 Ajax
$("#clothSaveField").on("submit", function(event){
event.preventDefault();
$.ajax({
type: "post",
url: "/updateCloth",
data: {
clothdata : $("#cloth").val(), //무한재귀가 뜬다면 val() 입력하지 않았는지 확인
category : $("#categoryForCloth").val()
},
datatype: "text",
success: function(){
viewMyClothes()
},
error: function(){
console.log("옷 저장 ajax 에러")
}
})
})// 옷 저장 Ajax
//옷 삭제 ajax
$(document).on("click", ".deleteEvent", function (){
var deleteClothID = $(this).data("id");
console.log(deleteClothID);
$.ajax({
type: "post",
url: "/deleteCloth",
data: {id: deleteClothID},
success: function(){
viewMyClothes()
},
error: function(){
console.log("옷 삭제 ajax 에러")
}
})
})
})//$(function()
function viewMyClothes(){
var allClothesField = $("#allClothesField")
$.ajax({
type: "post",
url: "/selectAllClothes",
success: function(response){
var tableHtml = "<table>";
tableHtml += "<tr><th>Category</th><th>Cloth</th><th>삭제</th></tr>";
response.forEach(function(cloth){
tableHtml += "<tr>";
tableHtml += "<td>" + cloth.category + "</td>";
tableHtml += "<td>" + cloth.clothdata + "</td>";
tableHtml += "<td class='deleteEvent' data-id='" + cloth.id + "'>" + "x" + "</td>";
tableHtml += "</tr>";
});
tableHtml += "</table>";
// 생성된 표를 화면에 출력
allClothesField.html(tableHtml);
},
error: function(){
console.log("저장된 옷 불러오기 실패")
}
})
}
- 해당 작업을 하는 도중, ajax data 속성의 데이터값의 val() 미표기로 인한 무한재귀 문제가 발생했다.
--> val() 표기를 통해 해결
무한재귀가 발생한 상황
data: {
clothdata : $("#cloth").val(),
category : $("#categoryForCloth").val()
},
에서 val()이 빠져 있었음
+ 데이터가 DB에 저장되지 않음
--> 출력 이전에 무한재귀에 빠진 상태
저장이 실패되는 오류가 뜨는게 아니라 무한재귀가 발생된 이유???
- val() 함수를 사용하지 않으면 jQuery는 해당 요소를 계속해서 참조하려고 시도하여 무한재귀가 발생하기 때문
- $("#cloth")는 jQuery 객체를 반환하며, 이 객체는 DOM 요소를 포함하고 있음
- 그러나 $("#cloth") 자체에 대해 다시 .val()을 호출하지 않고 그냥 $("#cloth")를 보내면 jQuery는 이를 자체적으로 평가하려고 시도
--> jQuery는 여전히 $("#cloth")를 평가하기 위해 자체를 호출하려고 하며, 이는 계속해서 재귀 호출을 발생
==> val() 함수를 사용하여 값을 명시적으로 추출하지 않으면 이러한 무한 재귀가 발생
- 또한 동적 생성과 초기 생성으로 인한 문제가 발생했다
--> 현재 표는 동적으로 재생성됨(옷 추가 / 삭제 시 새로 표를 불러옴)
--> 새로 생긴 옷에 대한 삭제 기능 추가가 필요
- 해당 문제는 이전 익명게시판의 댓글/대댓글 기능을 구현할 때 사용했던 방식을 통해 해결했다.
$(".deleteEvent").on("click", function ()
-->
$(document).on("click", ".deleteEvent", function ()
두 코드의 차이
$(document).on("click", ".deleteEvent", function ()
- 이벤트 위임을 사용하여 상위 요소에 이벤트 핸들러를 연결
- 동적으로 생성된 요소에 대해 일반적으로 사용
$(".deleteEvent").on("click", function ()
- 페이지 로드 시에 .deleteEvent 클래스를 가진 모든 요소에 이벤트 핸들러가 연결
- 동적으로 생성된 요소에는 이벤트가 자동으로 연결되지 않기 때문에 이 방법은 초기에 존재하는 정적인 요소에 대해서만 유효
# 개선점
- 옷 출력 메서드의 비효율성
--> 현재 전체 옷 목록을 가져온 뒤, 다시 개인의 옷을 출력한다는 비효율적인 코드 구성
==> 가져올 때부터 현재 로그인한 유저의 옷만 가져올 수 있도록 개선이 필요(jpa 코드)
- ClothVO가 MemberVO의 id값을 참조하고 있음을 나타내기 위해 MemberVO memberVO가 들어가야 하는듯? 하다
--> jpa 사용에 대해 미숙해서 제대로 왜 사용하는지 파악이 부족함
==>
MemberVO memberVO = new MemberVO();
memberVO.setId(updatedCloth.getUserid());
clothVO.setMemberVO(memberVO);
이런 식으로 사용해서 VO를 구성하는 방법이 타당한가? 에 대한 의문
필요에 따라서 개선 필요
- N+1 문제가 발생하는가?
--> 현재 해당 문제 디버깅까지 확인 못함
==> 추가 디버깅 작업을 통해 문제가 발생할 경우 해결 필수(fetch join 사용)
'개인프로젝트 > 기능프로그램_오늘뭐입지' 카테고리의 다른 글
오늘 뭐입지? 프로그램 관련 각종 문서 (0) | 2024.05.12 |
---|---|
20240511_날씨 데이터 출력 (0) | 2024.05.12 |
20240509_JPA기반 / 로그인 기능 구현 (0) | 2024.05.10 |
20240508_React 설정(멀티모듈, Intellij) (0) | 2024.05.08 |
20240507_jsp 설정&외부파일 설정 (0) | 2024.05.08 |