상품 수정

루민 ·2023년 4월 11일
0
post-thumbnail

📝상품 수정

등록된 상품 정보, 이미지를 수정하는 기능을 구현하도록 하겠습니다.



📝ItemForm

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ItemForm {


    private Long itemId;
    @NotEmpty(message = "상품 이름은 필수입니다.")
    private String name;  //상품명
    @NotNull(message = "상품 가격은 필수입니다.")
    private int price; //상품 가격
    @NotNull(message = "상품 재고 수량은 필수입니다.")
    private int stockQuantity;  //재고 수량
    private String description;  //상품 설명

    //상품 수정, 장바구니에 사용
    private List<ItemImageDto> itemImageListDto = new ArrayList<>();

    public ItemServiceDTO toServiceDTO() {
        return ItemServiceDTO.builder()
                .id(itemId)
                .name(name)
                .price(price)
                .stockQuantity(stockQuantity)
                .description(description)
                .build();
    }
  • 컨트롤러와 뷰와 통신할 때 쓰기위한 DTO


📝ItemController


@GetMapping("/items/{itemId}/edit")

@GetMapping("/items/{itemId}/edit")
    public String updateItemForm(@PathVariable(name = "itemId") Long itemId, Model model) {

        Item findItem = itemService.findItem(itemId);
        List<ItemImage> itemImageList = itemImageService.findItemImageDetail(itemId, "N");

        //엔티티 -> DTO로 변환
        List<ItemImageDto> itemImageListDto = itemImageList.stream()
                .map(ItemImageDto::new)
                .collect(Collectors.toList());


        ItemForm itemForm = new ItemForm(
                findItem.getId(),
                findItem.getName(),
                findItem.getPrice(),
                findItem.getStockQuantity(),
                findItem.getDescription(),
                itemImageListDto
        );

        model.addAttribute("itemForm", itemForm);

        return "item/updateItemForm";
    }
  • 상품 수정 폼
  • 바로 이전 포스트(상품 목록) 에서 상품 수정 버튼에 각자 아이템의 Primary key인 itemId를 넘기도록 구현하였습니다.
  • 상품 수정화면에는 기존의 상품 정보를 화면에 보여주어야 하기 때문에
  • Controller에서는 itemId를 받아와서 특정 아이템의 정보를 데이터베이스에서 찾아옵니다.
    //ItemService
    @Transactional(readOnly=true)
    public Item findItem(Long ItemId) {
        return itemRepository.findById(ItemId).orElse(null);
    }
    
    //== select * from Item where Item_Id = ItemId;
  • itemId와 삭제 여부를 통해 상품 이미지 정보를 데이터베이스에서 찾아오도록 하였습니다.
    //ItemImageService  
    //삭제 여부를 판단하여 상품 이미지 정보를 조회한다
    @Transactional(readOnly = true)
    public List<ItemImage> findItemImageDetail(Long itemId, String YN) {
        return itemImageRepository.findByItemIdAndDeleteYN(itemId, YN);
    }
  • 엔티티를 외부(View)로 반환하지 않기 위해 DTO로 변환해주는 작업을 진행하였습니다. (Item->ItemForm, ItemImageList -> ItemImageListDto)

@PostMapping

    @PostMapping("/items/{itemId}/edit")
    public String updateItem(@ModelAttribute ItemForm itemForm, Model model,
                             @RequestPart(name = "itemImages") List<MultipartFile> multipartFiles) throws IOException {

		List<ItemImage> findItemImages = itemImageService.findItemImageDetail(itemForm.getItemId(), "N");
        
        //상품 이미지를 등록안하면
        if (findItemImages.isEmpty() && multipartFiles.get(0).isEmpty()) {
            model.addAttribute("errorMessage", "상품 사진을 등록해주세요!");
            return "item/itemForm";
        }

        //상품 정보 수정
        itemService.updateItem(itemForm.toServiceDTO(), multipartFiles);

        return "redirect:/userHome";
    }
  • 관리자가 입력한 상품 수정 정보(ItemForm)를 받아와서 상품 수정을 진행합니다.
	//ItemService 
	//상품 정보 업데이트 (Dirty Checking, 변경감지)
    public void updateItem(ItemServiceDTO itemServiceDTO,  List<MultipartFile> multipartFileList) throws IOException {

        Item findItem = itemRepository.findById(itemServiceDTO.getId()).orElse(null);  //DB에서 찾아옴 -> 영속 상태
        findItem.updateItem(itemServiceDTO.getName(), itemServiceDTO.getDescription(), itemServiceDTO.getPrice(), itemServiceDTO.getStockQuantity());

        log.info("=====findItem={}", findItem.getName());

		//상품 이미지를 수정(삭제, 추가) 하지 않으면 실행 x 
        if(!multipartFileList.get(0).isEmpty()) {
            itemImageService.addItemImage(multipartFileList, findItem);
        }

    }
  • 데이터베이스에서 찾아온 findItem은 영속상태입니다.
  • findItem을 updateItem으로 값을 세팅해주면 스프링의 @Transactional에 의해서 트랜잭션이 commit됩니다.
  • 그 후 영속성 컨텍스트에서 변경된 건이 있는지 찾기 위해 flush(EntityManager.flush)를 진행하고 위와 같이 변경된 값이 있으면 Update Query를 데이터베이스에 날려고 업데이트를 진행합니다.
  • 상품 사진을 추가하기 위한 메소드 : addItemImage
  • 데이터베이스의 값 업데이트 완료
//상품 이미지 추가
    public void addItemImage(List<MultipartFile> multipartFiles, Item item) throws IOException {
        List<ItemImage> itemImages = fileHandler.storeImages(multipartFiles);

        for (ItemImage itemImage : itemImages) {
            item.addItemImage(itemImageRepository.save(itemImage));
        }
    }

@postMapping("item/delete")

	/**
     * 아이템 이미지 삭제 처리
     */
    @PostMapping("item/delete")
    public String deleteItemImage(@RequestParam("itemImageId") Long itemImageId, @RequestParam("itemId") Long itemId) {

        itemImageService.delete(itemImageId);

        return "redirect:/items/ " + itemId + "/edit";
    }
  • 상품 이미지를 삭제처리 하는 컨트롤러입니다.
  • 이미지를 삭제하는 방법은 데이터베이스의 내용을 실제로 삭제하는 법과 데이터베이스의 내용을 삭제하지 않고 필드의 삭제 플래그를 통해 삭제 로직을 구현하는 방법이 있습니다. 둘 다 장 단점이 있다고 하는데 저는 이번에 후자의 방법을 택했습니다.
  • 상품 수정 화면에서 삭제 버튼을 클릭(아래 쪽 참고)하면 삭제 처리되는 로직입니다.
  • 등록된 상품 이미지를 조회하고 싶을 때 ItemImage의 deleteYN이 'N'인 것을 조회하면 됩니다.
	//ItemImageSerivce
  	//데이터베이스를 삭제하지 않고 flag를 이용하여 처리
    public void delete(Long itemImageId) {
        ItemImage itemImage = itemImageRepository.findById(itemImageId).get();
        itemImage.deleteSet("Y");
    }


📝테스트

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class ItemServiceTest {

    @Autowired
    ItemRepository itemRepository;
    @Autowired
    ItemService itemService;

    private Item createItem() {
        return Item.createItem("후드 집업", "데상트 후드 집업", 70000, 300);
    }

    @Test
    @DisplayName("상품 수정 테스트")
    public void updateItem() {
        //given
        Item item = this.createItem();
        itemRepository.save(item);

        //when
        Item findItem = itemRepository.findById(item.getId()).orElse(null);
        findItem.updateItem("후드 집업", "지프 후드 집업", 55000, 300);

        //then
        Assert.assertEquals(item.getName(), "후드 집업");
        Assert.assertEquals(item.getPrice(), 55000);

    }
}


📝상품 수정 화면

<head>
    <meta charset="utf-8">
    <th:block layout:fragment="script">
        <script th:inline="javascript">
            var error = [[${errorMessage}]];
            if(error != null){
                alert(error);
            }
        </script>
    </th:block>
</head>
<body>
<section>
    <div layout:fragment="content" class="container">
        <div class="py-5 text-center">
            <h2>상품 수정</h2>
        </div>

        <form th:action th:object="${itemForm}" method="post" enctype="multipart/form-data">
            <div th:if="${#fields.hasGlobalErrors()}">
                <p class="field-error" th:each="err : ${#fields.globalErrors()}"
                   th:text="${err}">전체 오류 메시지</p>
            </div>

            <input type="hidden" th:field="*{itemId}">

            <div>
                <label for="name">상품명</label>
                <input type="text" id="name" th:field="*{name}" class="form-control" placeholder="상품명을 입력해주세요"
                       th:errorclass="field-error">
                <div class="field-error" th:errors="*{name}" />
            </div>
            <br>
            <div>
                <label for="price">가격</label>
                <input type="number" id="price" th:field="*{price}" class="form-control" placeholder="가격을 입력해주세요"
                       th:errorclass="field-error">
                <div class="field-error" th:errors="*{price}" />
            </div>
            <br>
            <div>
                <label for="stockQuantity">재고 수량</label>
                <input type="number" id="stockQuantity" th:field="*{stockQuantity}" class="form-control" placeholder="재고 수량을 입력해주세요"
                       th:errorclass="field-error">
                <div class="field-error" th:errors="*{stockQuantity}" />
            </div>
            <br>
            <div>
                <label for="description">상품 상세 설명</label>
                <textarea id="description" th:field="*{description}" class="form-control" aria-label="With textarea"></textarea>
            </div>
            <br>
            <br>
            <div>
                <label>상품 이미지</label>
                <div class="form-group" th:each="itemImage : ${itemForm.itemImageListDto}">
                    <div class="custom-file img-div">
                         <span th:text="${itemImage.originalName}">파일이름1.png</span>
                        <span>
                            <button th:fileId="${itemImage.id}" th:onclick="itemImgageDelete(this.getAttribute('fileId'))"
                             type="button" class="btn btn-outline-danger">삭제</button>
                        </span>
                    </div>
                </div>
            </div>
            
             <div class="mb-3">
                <label for="formFileMultiple" class="form-label">파일업로드</label>
                <input class="form-control" type="file" id="formFileMultiple" name="itemImages" multiple>
            </div>


            <br>
            <br>
            <div style="text-align:center">
                <button class="w-50 btn btn-primary btn-lg" th:align="center" type="submit">
                    상품 수정</button>
            </div>
            <br>
            <br>

        </form>
    </div> <!-- /container -->
</section>
</body>
</html>

<script>
    function itemImgageDelete(fileId){
        if (confirm("정말로 삭제하시겠습니까?")) {
            //배열생성
            const form = document.createElement('form');
            form.setAttribute('method', 'post');
            form.setAttribute('action', '/item/delete');

            //파일 id
            var input1 = document.createElement('input');
            input1.setAttribute("type", "hidden");
            input1.setAttribute("name", "itemImageId");
            input1.setAttribute("value", fileId);

            //게시판 id
            const selectedElements = document.querySelector("#itemId")
            var input2 = document.createElement('input');
            input2.setAttribute("type", "hidden");
            input2.setAttribute("name", "itemId");
            input2.setAttribute("value", selectedElements.value);

            form.appendChild(input1);
            form.appendChild(input2);
            document.body.appendChild(form);
            form.submit();
        }
    }
</script>

상품 수정 전


상품 수정 후


📝 수정화면 참고

0개의 댓글