이전에 등록 기능을 완성해보았다면 이번에는 수정과 삭제 기능을 추가해보자
진행과정!
그럼 먼저 홈화면에 내가 등록한 제품들이 보이게 설정해보자
indexController에 model로 itemList들을 보내줘야하는데 이때 Entity를 데이터교환에 사용하는것은 올바르지 않기때문에 해당 정보를 교환해줄 수 있는 Dto를 설정해준다.
ItemDto를 만들어주면 item은 member와 연관관계가 있기때문에 Member또한 해당 Dto를 만들어준다.
package com.qkrtprjs.happyexercise.dto;
import com.qkrtprjs.happyexercise.entitiy.member.Member;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import java.time.LocalDateTime;
@Getter
@NoArgsConstructor
public class ItemResponseDto {
private Long id;
private String detail;
private String name;
private int price;
private int stock; //재고
private String status;
private String imgName;
private String imgPath;
private MemberResponseDto memberResponseDto;
private LocalDateTime createdDate;
private LocalDateTime updatedDate;
@Builder
public ItemResponseDto(Long id, String detail, String name, int price, int stock, String status, String imgName,
String imgPath, MemberResponseDto memberResponseDto, LocalDateTime createdDate, LocalDateTime updatedDate) {
this.id = id;
this.detail = detail;
this.name = name;
this.price = price;
this.stock = stock;
this.status = status;
this.imgName = imgName;
this.imgPath = imgPath;
this.memberResponseDto = memberResponseDto;
this.createdDate = createdDate;
this.updatedDate = updatedDate;
}
}
package com.qkrtprjs.happyexercise.dto;
import com.qkrtprjs.happyexercise.entitiy.member.Role;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.time.LocalDateTime;
@Getter
@NoArgsConstructor
public class MemberResponseDto {
private Long id;
private String name;
private String email;
private String platform;
private String picture;
private Role role;
private LocalDateTime createdDate;
private LocalDateTime updatedDate;
@Builder
public MemberResponseDto(Long id, String name, String email, String platform, String picture, Role role, LocalDateTime createdDate, LocalDateTime updatedDate) {
this.id = id;
this.name = name;
this.email = email;
this.platform = platform;
this.picture = picture;
this.role = role;
this.createdDate = createdDate;
this.updatedDate = updatedDate;
}
}
@Transactional(readOnly = true)
public List<ItemResponseDto> findAll(){
return itemRepository.findAll().stream()
.map(item -> Item.toDto(item))
.collect(Collectors.toList());
}
@GetMapping("/")
private String index(Model model, @LoginMember SessionMember loginMember) {
model.addAttribute("loginMember", loginMember);
List<ItemResponseDto> itemResponseDtoList = itemService.findAll();
model.addAttribute("itemList", itemResponseDtoList);
return "index";
}
홈화면에 모델로 ItemList를 보내주었고 해당 정보가 잘 넘어 왔는지 확인하자
<!DOCTYPE html>
<html xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/default_layout}">
<div layout:fragment="content">
<div class="container mt-3">
<button type="button" class="btn btn-primary" onclick="location.href='/save/item'">상품 등록</button>
<br>
<br>
<h2>상품</h2>
<br>
<th:block th:each="item:${itemList}">
<div class="card" style="width:400px">
<img class="card-img-top" th:src="${item.imgPath}"
alt="Card image" style="width:100%">
<div class="card-body">
<h4 class="card-title" th:value="${item.name}">상품명</h4>
<p class="card-text">가격</p>
<a th:href="@{/item/${item.id}}" class="btn btn-primary" th:value="${item.detail}">자세히보기</a>
</div>
</div>
<br>
</th:block>
</div>
</div>
</html>
index화면을 설계하고 상품을 등록하고 결과를 확인했는데 아무것도 저장되어있지 않았다 분명히 저장이 정상적으로 작동이 되었는데 갑자기 저장이 되지않았다.
이전에 작성했던 js 파일가서 보니 폼 형태를 전달할때에 버튼을 submit이 아닌 button으로 지정시켜 놓고 버튼을 클릭시에 submit을 하고 location.href로 화면이동을 시켜주었는데let item = { init: function () { let _this = this; $('#save-btn').click(function () { _this.save(); location.href = '/'; }); }, save: function () { alert("제품을 등록하였습니다!"); $("#save-form").submit(); } } item.init();
이때 location.href를 빼주면 정상적으로 작동한다 그저 화면이동만 했을뿐인데 API 서비스가 작동되지않는 이유는 무엇일까...
해결방법은 RestController에서 리다이랙션을 사용하는 방법이 있었다.
@PostMapping("/api/item") private ResponseEntity<?> save(ItemSaveRequestDto itemSaveRequestDto, MultipartFile img, @LoginMember SessionMember member) throws Exception { Long id = itemService.save(itemSaveRequestDto, img, member); //RestController에서 리다이렉션 사용하는 방법 HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setLocation(URI.create("/")); return new ResponseEntity<>(httpHeaders, HttpStatus.MOVED_PERMANENTLY); }
이렇게 작성해주어서 리다이렉션이 가능하게 한다!
오류는 해결되었고 이어서 수정기능 API를 작성해보자, 먼저 제품을 상세하게 볼 수 있는 페이지를 만들어준뒤에 제품을 등록한 사람이 이 페이지를 보고있다면 수정, 삭제 버튼을 보이게 설정 해준다.
@GetMapping("/item/{id}")
private String detailItem(@PathVariable Long id, Model model, @LoginMember SessionMember loginMember) {
ItemResponseDto itemResponseDto = itemService.findById(id);
model.addAttribute("loginMember", loginMember);
model.addAttribute("item", itemResponseDto);
return "item/detail";
}
<!DOCTYPE html>
<html xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/default_layout}">
<div layout:fragment="content">
<div class="container mt-3">
<h2>상품 등록</h2>
<th:block th:if="${loginMember.email==item.memberResponseDto.email}">
<button type="button" class="btn btn-warning" id="update-btn" th:onclick="|location.href='@{|/updateForm/item/${item.id}|}'|">수정</button>
<button type="button" class="btn btn-danger" id="delete-btn">삭제</button>
</th:block>
<form action="/api/item" id="save-form" method="post" enctype="multipart/form-data">
<div class="mb-3 mt-3">
<label for="name">상품명:</label>
<input type="text" class="form-control" id="name" th:value="${item.name}" name="name" readonly>
</div>
<div class="mb-3">
<label for="detail">상품 설명:</label>
<input type="text" class="form-control" id="detail" th:value="${item.detail}" name="detail" readonly>
</div>
<div class="mb-3">
<label for="price">가격:</label>
<input type="number" class="form-control" id="price" th:value="${item.price}" name="price" readonly>
</div>
<div class="mb-3">
<label for="stock">재고:</label>
<input type="number" class="form-control" id="stock" th:value="${item.stock}" name="stock" readonly>
</div>
<div class="mb-3">
<label for="status">상태:</label>
<input type="text" class="form-control" id="status" th:value="${item.status}" name="status" readonly>
</div>
<div class="mb-3">
<label for="img">사진:</label>
<br>
<img th:src="${item.imgPath}" id="img" class="form" width="304" height="236" >
</div>
</form>
</div>
</div>
</html>
수정 버튼을 클릭했을때에 수정 가능한 폼으로 이동 시켜준다.
<button type="button" class="btn btn-warning" id="update-btn" th:onclick="|location.href='@{|/updateForm/item/${item.id}|}'|">수정</button>
<!DOCTYPE html>
<html xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/default_layout}">
<div layout:fragment="content">
<div class="container mt-3">
<h2>상품 수정</h2>
<form th:action="@{|/api/item/${item.id}|}" th:method="put" enctype="multipart/form-data">
<input type="hidden" name="id" th:value="${item.id}">
<div class="mb-3 mt-3">
<label for="name">상품명:</label>
<input type="text" class="form-control" id="name" th:value="${item.name}" name="name">
</div>
<div class="mb-3">
<label for="detail">상품 설명:</label>
<input type="text" class="form-control" id="detail" th:value="${item.detail}" name="detail">
</div>
<div class="mb-3">
<label for="price">가격:</label>
<input type="number" class="form-control" id="price" th:value="${item.price}" name="price">
</div>
<div class="mb-3">
<label for="stock">재고:</label>
<input type="number" class="form-control" id="stock" th:value="${item.stock}" name="stock">
</div>
<div class="mb-3">
<label for="status">상태:</label>
<input type="text" class="form-control" id="status" th:value="${item.status}" name="status">
</div>
<div class="mb-3">
<label for="img">현재 사진:</label>
<br>
<img th:src="${item.imgPath}" class="form" width="304" height="236" >
</div>
<div class="mb-3">
<label for="img">수정 할 사진:</label>
<input type="file" class="form-control" id="img" placeholder="사진을 첨부해주세요..." name="img"
multiple="multiple">
</div>
<button type="submit" class="btn btn-primary" id="update-btn">수정 완료</button>
</form>
</div>
</div>
</html>
처음에 수정폼 작성할때에 rest 방식을 따라야하기때문에 put 방법으로 데이터를 전달하려고했으나 HTML의 Form형태는 전송방법이 get과 POST 방법밖에 없었다. 하지만 알아보니 타임리프를 통해서 변경할 수가 있었다.
먼저 YML에 spring.mvc.hiddenmethod.filter.enabled=true 로 설정해준뒤에
폼 태그 안에<form th:action="@{|/api/item/${item.id}|}" th:method="put" enctype="multipart/form-data">
타임리프를 사용해서 폼태크를 설정해주게되면 PUT 방식으로도 전달할 수가 있었다.
@PutMapping("/api/item/{id}")
private ResponseEntity<?> update(@PathVariable Long id, ItemUpdateRequestDto itemUpdateRequestDto, MultipartFile img,
@LoginMember SessionMember member) throws Exception {
System.out.println(id);
itemService.update(itemUpdateRequestDto, img, member);
//RestController에서 리다이렉션 사용하는 방법
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setLocation(URI.create("/item/" + id));
return new ResponseEntity<>(httpHeaders, HttpStatus.MOVED_PERMANENTLY);
}
@Transactional
public Long update(ItemUpdateRequestDto itemUpdateRequestDto, MultipartFile img, SessionMember member) throws Exception{
String imgName = null;
if (!img.getOriginalFilename().equals("")) {
imgName = saveImg(img);
Member loginMember = memberRepository.findByEmail(member.getEmail()).orElseThrow(() -> new IllegalArgumentException("해당 이메일은 존재하지않습니다"));
Item item = itemUpdateRequestDto.toEntity(itemUpdateRequestDto, imgName, "/image/"+imgName, loginMember);
return itemRepository.save(item).getId();
}else{
Member loginMember = memberRepository.findByEmail(member.getEmail()).orElseThrow(() -> new IllegalArgumentException("해당 이메일은 존재하지않습니다"));
Item item = itemRepository.findById(itemUpdateRequestDto.getId()).orElseThrow(() -> new IllegalArgumentException("해당 아이디값은 존재하지않습니다!"));
Item updateItem=item.update(itemUpdateRequestDto);
return itemRepository.save(updateItem).getId();
}
}
사진을 수정했을떄와 수정하지 않았을때를 판단해서 따로 진행하였다.
- 그럼 다음에 삭제를 진행해보자!!