나만의 프로젝트 배포하기 7일차

박세건·2023년 9월 15일
0

개인 프로젝트

목록 보기
7/15

이전에 등록 기능을 완성해보았다면 이번에는 수정과 삭제 기능을 추가해보자

수정과 삭제 API

진행과정!

  • 먼저 홈화면에 내가 등록한 제품들이 보이게 설정
  • 제품 클릭시에 자세히 볼수있는 페이지로 이동
  • 해당 제품을 등록한 사람이라면 제품 수정, 삭제 버튼이 보이게 설정

그럼 먼저 홈화면에 내가 등록한 제품들이 보이게 설정해보자
indexController에 model로 itemList들을 보내줘야하는데 이때 Entity를 데이터교환에 사용하는것은 올바르지 않기때문에 해당 정보를 교환해줄 수 있는 Dto를 설정해준다.
ItemDto를 만들어주면 item은 member와 연관관계가 있기때문에 Member또한 해당 Dto를 만들어준다.

  • ItemResponseDto
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;
    }
}
  • MemberResponseDto
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;
    }
}
  • dto가 Entity를 참조하는 것은 말이 안되기때문에 Dto끼리 연관관계를 맺어주었고 Item List를 갖고올 수 있는 메서드를 서비스에 추가한다.
    @Transactional(readOnly = true)
    public List<ItemResponseDto> findAll(){
        return itemRepository.findAll().stream()
                .map(item -> Item.toDto(item))
                .collect(Collectors.toList());
    }
  • IndexController
    @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를 보내주었고 해당 정보가 잘 넘어 왔는지 확인하자

  • index.html
<!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";
    }
  • detail.html
<!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>
  • 수정 Form Html
<!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 방식으로도 전달할 수가 있었다.

  • 데이터를 받아서 update해주는 API Controller
    @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();
        }
    }

사진을 수정했을떄와 수정하지 않았을때를 판단해서 따로 진행하였다.

  • 그럼 다음에 삭제를 진행해보자!!
profile
멋있는 사람 - 일단 하자

0개의 댓글