Jin's Dev Story

[스프링 부트 쇼핑몰 프로젝트 with JPA] 6-1. [상품 등록] Entity, DTO, View 본문

Web & Android/스프링 부트 쇼핑몰 프로젝트 with JPA

[스프링 부트 쇼핑몰 프로젝트 with JPA] 6-1. [상품 등록] Entity, DTO, View

woojin._. 2023. 10. 15. 10:33
이 내용은 스프링 부트 쇼핑몰 프로젝트 with JPA 책을 학습한 내용입니다.

1. 상품 이미지 Entity

  • Item (상품) Entity 와 다대일 단방향 관계를 갖는 ItemImg Entity 생성
package kr.spring.item.entity;

import jakarta.persistence.*;
import kr.spring.utils.entity.BaseEntity;
import lombok.*;

@Entity
@Getter
@Setter // 필수 아님
@ToString // 문자열 자동 생성
@NoArgsConstructor // 빈생성자 생성
public class ItemImg extends BaseEntity {

    @Id //기본키 설정
    @GeneratedValue(strategy = GenerationType.IDENTITY) //mysql의 경우 identity를 사용.
    @Column(name = "item_img_id")  //name 속성은 column의 이름을 변경할 수 있다.
    private Long id;                     // 이미지 코드

    private String imgName; // 이미지 파일명

    private String oriImgName; // 원본 파일명

    private String imgUrl; // 이미지 경로

    private String repImgYn; // 대표 이미지 여부

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "item_id")
    private Item item;

    public void updateItemImg(String oriImgName, String imgName, String imgUrl) {
        this.oriImgName = oriImgName;
        this.imgName = imgName;
        this.imgUrl = imgUrl;
    }
}

2. modelmapper 라이브러리 추가

// modelmappter 추가 -> dto <-> entity
    implementation 'org.modelmapper:modelmapper:3.1.1'
  • DTO 객체와 Entity 객체의 변환을 도와줌
  • 서로 다른 클래스의 값을 필드의 이름과 자료형이 같으면 getter, setter를 통해 값을 복사해서 객체를 반환

3. 상품 관련 DTO 객체 생성

  • 상품을 등록 및 조회할 때 지정된 필드뿐 아니라 추가적인 데이터들의 이동이 많으므로 여러 DTO 이용
  • 상품 이미지에 대한 DTO
  • ItemImg 엔티티 객체를 ItemImgDto 객체로 변환
package kr.spring.item.dto;

import kr.spring.item.entity.ItemImg;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.modelmapper.ModelMapper;

@Getter
@Setter
@ToString // 내용 확인을 위해 사용
public class ItemImgDto {

    private Long id;  // 이미지 코드

    private String imgName; // 이미지 파일명

    private String oriImgName; // 원본 파일명

    private String imgUrl; // 이미지 경로

    private String repImgYn; // 대표 이미지 여부

    private static ModelMapper modelMapper = new ModelMapper();

    // 엔티티로 변환
    public static ItemImgDto of(ItemImg itemImg) {
        return modelMapper.map(itemImg, ItemImgDto.class); // mapping 걸기
    }
}
  • 화면으로부터 입력 받은 상품 데이터 정보 DTO
  • Item 엔티티 객체와 DTO 객체 간의 변환
package kr.spring.item.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import kr.spring.item.entity.Item;
import lombok.Getter;
import lombok.Setter;
import org.modelmapper.ModelMapper;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Getter // 반드시 붙여야 함
@Setter // 필수는 아님
public class ItemFormDto {

    private Long id;                     // 상품 코드

    @NotBlank(message = "상품명은 필수 항목입니다.")
    private String itemNm;               // 상품 이름

    @NotNull(message = "가격은 필수 항목입니다.")
    private int price;                   // 상품 가격

    @NotNull(message = "재고는 필수 항목입니다.")
    private int stockNumber;             // 재고 수량

    private String itemSellStatus;

    @NotBlank(message = "상품 설명은 필수 항목입니다.")
    private String itemDetail;           // 상품 상세 설명

    private List<ItemImgDto> itemImgDtoList = new ArrayList<>(); // imgDtoList 관리

    private List<Long> itemImgList = new ArrayList<>(); // 이미지들에 대한 번호 관리

    private static ModelMapper modelMapper = new ModelMapper(); // 엔티티와 매핑

    public Item createItem() {
        return modelMapper.map(this, Item.class); // dto를 엔티티로 변환
    }

    public static ItemFormDto of(Item item) {
        return modelMapper.map(item, ItemFormDto.class); // 엔티티를 dto로 변환
    }
}

4. ItemController

  • ItemFormDto 객체를 model 객체에 담아서 뷰로 전달
@Controller
@RequiredArgsConstructor // @Autowired로도 쓸 수 있음
public class ItemController {

    // 웹 페이지로 이동
    @GetMapping("/admin/item/new")
    public String itemForm(Model model) {
        model.addAttribute("itemFormDto", new ItemFormDto());

        return "item/itemForm";
    }

5. ItemForm View

  • 상품 판매 상태를 “판매중” 또는 “품절”로 선택할 수 있게 <select> 태그 이용

<!-- 상품 판매 상태는 판매 중/품절로 나뉜다. -->
            <div class="form-group">
                  <select th:field="*{itemSellStatus}" class="custom-select">
                        <option value="SELL">판매중</option>
                        <option value="SOLD_OUT">품절</option>
                  </select>
            </div>
  • 상품을 처음 등록할 경우 Controller를 통해 전달 받은 itemFormDtoList가 존재하지 않음
  • Thymeleaf builtin 메소드 #numbers.sequence() 를 통해서 1 부터 5 까지 상품이미지명 출력
<!--            #numbers.sequence(start,end)를 이용하여 반복처리. 최대 이미지 등록 갯수는 5개. -->
                  <div class="form-group" th:each="num: ${#numbers.sequence(1,5)}">
                        <div class="custom-file img-div">
                              <!--  나중에 name에 해당되는 itemImgFile을 들고 날라간다 똑같은 이름이 다섯 번 나타날 때 얘가 어떻게 처리되는지를 봐야 함 -->
                              <input type="file" class="custom-file-input" name="itemImgFile">
                              <!--                    몇 번째 상품 이미지인지 표시 -->
                              <label class="custom-file-label" th:text="상품이미지 + ${num}"></label>
                        </div>
                  </div>
  • 상품을 수정할 경우는 이미 존재하던 상품 이미지가 있으므로 itemFormDtoList가 존재함
  • 이미지가 존재하면 해당 이미지의 이름을 출력하고 빈칸이면 상품이미지+index를 출력
<!--  비어있지 않고 뭔가 들고 날라온 경우 그러니까 수정을 위함 -->
            <div th:if="${not #lists.isEmpty(itemFormDto.itemImgDtoList)}">
                  <div class="form-group"
                       th:each="itemImgDto, status: ${itemFormDto.itemImgDtoList}">
                        <div class="custom-file img-div">
                              <!--  일단 거기에 있는 정보를 다 가지고 올 거야  -->
                              <input type="file" class="custom-file-input" name="itemImgFile">
                              <!--                특정 상품 수정/삭제 시 어떤 이미지가 수정됐는지 알기 위해 상품 이미지의 아이디를 hidden으로 숨겨둔다. -->
                              <input type="hidden" name="itemImgIds" th:value="${itemImgDto.id}">
                              <label class="custom-file-label"
                                     th:text="${not #strings.isEmpty(itemImgDto.oriImgName)} ? ${itemImgDto.oriImgName} : '상품이미지' + ${status.index+1}"></label>
                        </div>
                  </div>
            </div>
  • 상품을 처음 등록할 때는 “저장”버튼을 출력
  • 상품을 수정할 때는 “수정”버튼을 출력
!--상품아이디가 없는 경우 저장 로직을 호출하는 버튼 보여줌. 원래 form 태그에서 지정해줘야하지만 아이디를 체크해서 없는 경우 fromaction을 이용하여 new로 준다-->
            <div th:if="${#strings.isEmpty(itemFormDto.id)}"
                 style="text-align: center">
                  <button th:formaction="@{'/admin/item/new'}" type="submit"
                          class="btn btn-primary">저장</button>
            </div>
            <!--        저장된 이미지 정보가 있다면 파일의 이름을 보여주고, 없다면 상품 이미지 + 번호를 보여준다. -->
            <div th:unless="${#strings.isEmpty(itemFormDto.id)}"
                 style="text-align: center">
                  <!--            상품 아이디가 있는 경우 수정 로직을 호출하는 벼튼을 보여줌-->
                  <button th:formaction="@{'/admin/item/' + ${itemFormDto.id} }"
                          type="submit" class="btn btn-primary">수정</button>
            </div>

6. 상품 등록 페이지 화면